Eugeny/tabby · error · Error

Cannot open shell channel before auth

Error message

Cannot open shell channel before auth

What it means

Thrown by openShellChannel() when this.ssh is not yet an AuthenticatedSSHClient. Opening a shell needs an authenticated session to call openSessionChannel / activateChannel / requestPTY. The shell session (shell.ts:43) calls this to obtain the interactive PTY channel.

Source

Thrown at tabby-ssh/src/session/ssh.ts:857

                throw new Error('Cannot remove remote port forward before auth')
            }
            this.ssh.stopForwardingTCPPort(fw.host, fw.port)
            this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
        }
        this.emitServiceMessage(`Stopped forwarding ${fw}`)
    }

    async destroy (): Promise<void> {
        this.logger.info('Destroying')
        this.willDestroy.next()
        this.willDestroy.complete()
        this.serviceMessage.complete()
        this.ssh.disconnect()
    }

    async openShellChannel (options: { x11: boolean }): Promise<russh.Channel> {
        if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
            throw new Error('Cannot open shell channel before auth')
        }
        const ch = await this.ssh.activateChannel(await this.ssh.openSessionChannel())
        await ch.requestPTY('xterm-256color', {
            columns: 80,
            rows: 24,
            pixHeight: 0,
            pixWidth: 0,
        })
        if (options.x11) {
            await ch.requestX11Forwarding({
                singleConnection: false,
                authProtocol: 'MIT-MAGIC-COOKIE-1',
                authCookie: crypto.randomBytes(16).toString('hex'),
                screenNumber: 0,
            })
        }
        if (this.profile.options.agentForward) {
            await ch.requestAgentForwarding()

View on GitHub (pinned to 14e2d60b9b)

Solutions

  1. Await session.start() and confirm authentication succeeded before creating the shell / calling openShellChannel.
  2. Gate on the type check: only open the shell channel when this.ssh instanceof russh.AuthenticatedSSHClient.
  3. Re-open the shell only after a successful re-auth on reconnect, not on the connection event alone.
  4. Surface auth failures to the shell consumer so it does not attempt to open a channel on a dead session.

Example fix

// before (shell.ts)
this.shell = await this.ssh.openShellChannel({ x11: this.profile.options.x11 })
// after
if (this.ssh.ssh instanceof russh.AuthenticatedSSHClient) {
    this.shell = await this.ssh.openShellChannel({ x11: this.profile.options.x11 })
} else {
    throw new Error('SSH session not authenticated')
}
Defensive patterns

Strategy: type-guard

Validate before calling

import * as russh from 'russh'
if (!(ssh.ssh instanceof russh.AuthenticatedSSHClient)) {
    throw new Error('Cannot open shell: SSH session not authenticated yet')
}

Type guard

import * as russh from 'russh'
function isOpen (s: unknown): s is russh.AuthenticatedSSHClient {
    return s instanceof russh.AuthenticatedSSHClient
}

Prevention

When it happens

Trigger: openShellChannel({x11}) called before session.start()/auth completed, or after the transport dropped and reverted to an unauthenticated SSHClient. The consumer in shell.ts calls it right after constructing the shell, expecting the parent SSH session to be ready.

Common situations: Opening a terminal tab before the SSH handshake finishes; reconnect race where the shell is (re)opened during re-authentication; auth failed silently and the shell component still tries to start; programmatic automation that opens a channel without awaiting start().

Related errors


AI-assisted analysis of Eugeny/tabby@14e2d60b9b (2026-08-12). Data as JSON: /api/errors/466b0e26487132e1. Report an issue: GitHub.