How Tabby Integrates with the Windows OpenSSH Agent: A Deep Dive into Named Pipe Authentication

Tabby connects to the Windows OpenSSH ssh-agent through the named pipe \\.\pipe\openssh-ssh-agent using the russh library, automatically detecting the pipe in tabby-ssh/src/session/ssh.ts and forwarding authentication challenges via SSHAgentStream.

Tabby, the open-source terminal emulator maintained by Eugeny, implements Windows OpenSSH Agent integration through its tabby-ssh module. This integration allows Windows users to leverage existing ssh-agent key material stored in the Windows OpenSSH service without manual password entry. The implementation relies on the russh Rust library and involves specific detection logic for the OpenSSH named pipe alongside fallback support for Pageant.

Agent Discovery via Named Pipe Detection

The core of Tabby's Windows OpenSSH integration resides in SSHSession#getAgentConnectionSpec() within tabby-ssh/src/session/ssh.ts (lines 296-330). This method implements the automatic discovery logic that determines which local agent to use for SSH authentication.

When agentType is set to "auto" (the default defined in tabby-ssh/src/config.ts), Tabby first checks for the existence of the Windows OpenSSH named pipe at \\.\pipe\openssh-ssh-agent using fs.stat. If the pipe exists, the method returns a russh.AgentConnectionSpec with kind: 'named-pipe' pointing to this path. If the pipe is absent, Tabby queries russh.isPageantRunning() to detect Pageant as a fallback mechanism.

Configuration Options in SSHConfigProvider

The default behavior is controlled by SSHConfigProvider in tabby-ssh/src/config.ts (lines 5-11), which exposes the following settings:

  • ssh.agentType: Accepts "auto", "pageant", or "custom" to control agent selection
  • ssh.agentPath: Optional string specifying a custom named pipe path when agentType is set to "custom"

When agentType is "custom", Tabby bypasses automatic detection and connects directly to the pipe specified in agentPath.

The Agent Authentication Workflow

Once the agent specification is resolved, Tabby prepares authentication methods during session initialization. In tabby-ssh/src/session/ssh.ts (lines 298-328), the init() method constructs an array of AuthMethod objects. If the profile uses auth: "agent" or has private keys configured, Tabby loads corresponding .pub files to create specific agent-identity methods, followed by a general agent fallback that allows the agent to try all loaded keys.

When the remote server requests agent forwarding, the subscription at lines 35-52 in the same file handles the agentChannelOpen$ event. This creates a bidirectional data pipe between the SSH channel and the local agent:

this.ssh.agentChannelOpen$.subscribe(async newChannel => {
  if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
    throw new Error('Cannot open agent channel before auth')
  }
  const channel = await this.ssh.activateChannel(newChannel)
  const spec = await this.getAgentConnectionSpec()
  if (!spec) { await channel.close(); return }

  const agent = await russh.SSHAgentStream.connect(spec)
  channel.data$.subscribe(data => agent.write(data))
  agent.data$.subscribe(data => channel.write(data), undefined, () => channel.close())
  channel.closed$.subscribe(() => agent.close())
})

This real-time piping allows the remote server to request signatures from your local Windows OpenSSH agent while maintaining session security.

Practical Configuration Examples

Enabling Windows OpenSSH Agent in a Profile

To use the Windows OpenSSH agent with automatic detection, configure your Tabby profile with the following JSON structure:

{
  "name": "My Windows Host",
  "type": "ssh",
  "options": {
    "host": "my.host.com",
    "port": 22,
    "user": "myuser",
    "auth": "agent",
    "privateKeys": [
      "%h/.ssh/id_rsa"
    ]
  },
  "store": {
    "ssh": {
      "agentType": "auto",
      "agentPath": null
    }
  }
}

With this configuration, Tabby calls getAgentConnectionSpec(), discovers the \\.\pipe\openssh-ssh-agent pipe, and authenticates using keys loaded in the agent. The privateKeys array is optional but recommended when you want Tabby to pre-load specific public keys for agent-identity authentication.

Specifying a Custom Named Pipe

For scenarios requiring a non-standard agent location, bypass automatic detection:

{
  "store": {
    "ssh": {
      "agentType": "custom",
      "agentPath": "\\\\.\\pipe\\my-custom-agent"
    }
  }
}

Setting "agentType": "custom" forces Tabby to connect directly to the supplied pipe path without checking for the standard OpenSSH pipe or Pageant.

Summary

  • Tabby integrates with the Windows OpenSSH Agent through the named pipe \\.\pipe\openssh-ssh-agent using the russh library's SSHAgentStream interface.
  • Automatic detection occurs in SSHSession#getAgentConnectionSpec() at tabby-ssh/src/session/ssh.ts (lines 296-330), which checks for the pipe's existence via fs.stat before falling back to Pageant.
  • Configuration is controlled by SSHConfigProvider in tabby-ssh/src/config.ts, offering agentType options of "auto", "pageant", or "custom".
  • Agent forwarding is handled through bidirectional piping between the SSH channel and local agent in the agentChannelOpen$ subscription at lines 35-52 of the session file.
  • Public key pre-loading occurs when private key paths are configured, allowing Tabby to specify exact agent identities before falling back to general agent authentication.

Frequently Asked Questions

Does Tabby require manual configuration to use the Windows OpenSSH agent?

No, Tabby automatically detects the standard Windows OpenSSH agent pipe at \\.\pipe\openssh-ssh-agent when agentType is set to "auto" (the default). The detection logic in tabby-ssh/src/session/ssh.ts checks for pipe existence during session initialization, requiring no manual path configuration unless you are using a custom agent location.

Can Tabby fall back to Pageant if the OpenSSH agent is not running?

Yes, when agentType is "auto", Tabby implements a two-step discovery process. If fs.stat fails to locate the OpenSSH named pipe, the code calls russh.isPageantRunning() to check for a running Pageant instance. If Pageant is detected, Tabby returns a connection spec with kind: 'pageant' instead of the named-pipe configuration, allowing seamless fallback without user intervention.

How does Tabby handle agent forwarding requests from remote servers?

When a remote server opens an agent channel, Tabby's agentChannelOpen$ subscription in tabby-ssh/src/session/ssh.ts creates an russh.SSHAgentStream connected to the spec returned by getAgentConnectionSpec(). The implementation uses RxJS observables to pipe data bidirectionally: data from the SSH channel is written to the local agent via agent.write(), and responses from the agent are written back to the channel via channel.write(), enabling secure agent forwarding to remote hosts.

What is the difference between agentType: "auto" and "pageant"?

Setting agentType to "auto" instructs Tabby to prefer the Windows OpenSSH named pipe and automatically fall back to Pageant only if the pipe is unavailable. Conversely, setting it to "pageant" forces Tabby to skip the named pipe detection entirely and connect directly to Pageant, which is useful when both agents are running but you specifically want to use Pageant's key store.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →