# How Tabby's Zmodem File Transfer Works Over SSH: A Deep Dive into the Implementation

> Discover how Tabby implements Zmodem file transfer over SSH. Learn how it seamlessly integrates with terminal sessions, using native pickers for uploads and downloads.

- Repository: [Eugene/tabby](https://github.com/Eugeny/tabby)
- Tags: deep-dive
- Published: 2026-03-03

---

**Tabby implements Zmodem file transfer over SSH as a terminal-session middleware that intercepts Zmodem handshake sequences in the SSH data stream, automatically triggering native file pickers for uploads or save dialogs for downloads while streaming file contents through the Zmodem protocol.**

Tabby, the popular open-source terminal emulator maintained by Eugeny, provides seamless file transfer capabilities over SSH using the legacy Zmodem protocol. Unlike standalone SFTP clients, Tabby's implementation operates directly within your active terminal session, responding to standard `rz` and `sz` commands without requiring additional connections. This article examines the TypeScript source code in the `Eugeny/tabby` repository to reveal how the terminal detects, manages, and executes bidirectional file transfers through three distinct architectural layers.

## Middleware Architecture and Detection

Tabby's Zmodem support functions as a **terminal-session middleware** that sits between the SSH stream and the terminal UI. The core logic resides in [`tabby-terminal/src/features/zmodem.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-terminal/src/features/zmodem.ts), where the `ZModemMiddleware` class monitors every byte transmitted through the session.

The detection process begins with `ZModemMiddleware.feedFromSession`, which constantly watches incoming SSH data for Zmodem signatures. When the internal **ZModem Sentry** recognizes a start-of-transfer sequence, it invokes the `on_detect` callback, triggering `process()` to create a new session【L51-L57】. The `detection.confirm()` method instantiates a session object that determines whether the remote side intends to send or receive files based on the Zmodem handshake【L75-L81】.

## Uploading Files to Remote Hosts

When the remote host executes the `rz` command (indicating it is ready to receive), Tabby detects this as a **send session** (`type === 'send'`). The middleware immediately invokes the platform layer to open a native file-picker dialog.

According to the source in [`tabby-electron/src/services/platform.service.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-electron/src/services/platform.service.ts), the `startUpload()` method (lines 203-215) returns an array of `FileUpload` objects representing the user's selections. For each file, `sendFile()` constructs a Zmodem offer containing the filename, size, mode, and byte counters【L68-L75】. Once the remote side accepts the offer, Tabby streams the file in chunks using `transfer.read()` and `xfer.send(chunk)`, updating a live progress indicator in the terminal UI【L86-L95】.

```typescript
// Triggered when user initiates upload via UI or remote runs 'rz'
const uploads: FileUpload[] = await this.platform.startUpload({multiple: true});
// Each file is offered to the remote session via sendFile()

```

## Downloading Files from Remote Hosts

Conversely, when the remote host executes `sz filename` to send a file, Tabby detects a **receive session** (`type === 'receive'`). The middleware listens for `zsession.on('offer')` events and invokes `receiveFile()` for each incoming file offer【L94-L100】.

The platform layer's `startDownload` method displays a "Save As" dialog and returns a `FileDownload` object (lines 55-71 in [`platform.service.ts`](https://github.com/Eugeny/tabby/blob/main/platform.service.ts)). As the Zmodem library emits data chunks through the `on_input` event, the middleware writes them to the local filesystem via `transfer.write()` while displaying transfer percentages in the terminal【L141-L149】.

```bash

# On the remote SSH host - triggers Tabby's save dialog

sz myfile.txt

```

## Session Cancellation and Cleanup

Tabby provides immediate transfer cancellation through standard terminal interrupts. The middleware subscribes to `cancelEvent`, which filters the output stream for ASCII character 3 (Ctrl-C)【L24-L30】. Upon detection, both sending and receiving paths abort the active Zmodem session and dispose of temporary `FileUpload` or `FileDownload` objects to prevent resource leaks【L131-L138】.

```typescript
// Cancellation logic observes for ASCII 3 (Ctrl-C)
this.cancelEvent = this.outputToSession$.pipe(
  filter(x => x.length === 1 && x[0] === 3)
);

```

## Automatic UI Integration

The `ZModemDecorator` class ensures Zmodem functionality is available in every terminal tab without manual configuration. When a new terminal session initializes, the decorator injects the middleware using `terminal.session.middleware.unshift(new ZModemMiddleware(...))`【L49-L55】. This automatic hooking makes Zmodem transfers available for any SSH, Telnet, or serial connection managed by Tabby.

## Summary

- **Middleware-based detection**: The `ZModemMiddleware` class in [`tabby-terminal/src/features/zmodem.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-terminal/src/features/zmodem.ts) intercepts all SSH traffic to identify Zmodem handshakes using a Sentry pattern.
- **Bidirectional transfers**: Tabby distinguishes between send sessions (uploading via `rz`) and receive sessions (downloading via `sz`), invoking appropriate platform dialogs for each direction.
- **Platform abstraction**: File I/O operations are delegated to [`tabby-electron/src/services/platform.service.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-electron/src/services/platform.service.ts), which handles native Electron dialogs and streaming interfaces.
- **Automatic availability**: The decorator pattern ensures every new terminal tab includes Zmodem middleware without user configuration.
- **Graceful cancellation**: ASCII 3 (Ctrl-C) detection provides immediate abort capabilities for stuck or unwanted transfers.

## Frequently Asked Questions

### What commands trigger Tabby's Zmodem file transfer?

Tabby responds to standard Zmodem commands executed on the remote host. Running `rz` (receive Zmodem) triggers an upload dialog for sending local files to the remote server, while `sz filename` triggers a save dialog for downloading files from the remote server to your local machine.

### How does Tabby differentiate between uploading and downloading files?

The implementation inspects the Zmodem session type established during the initial handshake. A `type === 'send'` session indicates the remote is ready to receive files, prompting Tabby to open an upload dialog. A `type === 'receive'` session indicates the remote is sending files, triggering Tabby's download dialog and file save logic.

### Can I cancel an ongoing Zmodem transfer in Tabby?

Yes, pressing **Ctrl-C** (sending ASCII character 3) during an active transfer immediately aborts the session. The middleware listens for this specific byte sequence through the `cancelEvent` observable and cleans up temporary file handlers for both uploads and downloads.

### Is Zmodem support available for all connection types in Tabby?

Yes, because the `ZModemDecorator` automatically injects the middleware into every new terminal session using `terminal.session.middleware.unshift()`, Zmodem file transfers work across SSH, Telnet, and serial connections without requiring protocol-specific configuration.