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
- Await session.start() and confirm authentication succeeded before creating the shell / calling openShellChannel.
- Gate on the type check: only open the shell channel when this.ssh instanceof russh.AuthenticatedSSHClient.
- Re-open the shell only after a successful re-auth on reconnect, not on the connection event alone.
- 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
- Await session.start() and confirm auth before creating the shell.
- On reconnect, re-open the shell only after re-auth succeeds.
- Propagate auth-failure events to shell consumers so they don't open on a dead session.
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
- Cannot add remote port forward before auth
- Authentication rejected
- Cannot remove remote port forward before auth
- Download cancelled
- ${profile.options.host}: jump host "${profile.options.jumpHo
AI-assisted analysis of Eugeny/tabby@14e2d60b9b (2026-08-12).
Data as JSON: /api/errors/466b0e26487132e1.
Report an issue: GitHub.