{"record":{"id":"466b0e26487132e1","repo":"Eugeny/tabby","slug":"cannot-open-shell-channel-before-auth","errorCode":null,"errorMessage":"Cannot open shell channel before auth","messagePattern":"Cannot open shell channel before auth","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tabby-ssh/src/session/ssh.ts","lineNumber":857,"sourceCode":"                throw new Error('Cannot remove remote port forward before auth')\n            }\n            this.ssh.stopForwardingTCPPort(fw.host, fw.port)\n            this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)\n        }\n        this.emitServiceMessage(`Stopped forwarding ${fw}`)\n    }\n\n    async destroy (): Promise<void> {\n        this.logger.info('Destroying')\n        this.willDestroy.next()\n        this.willDestroy.complete()\n        this.serviceMessage.complete()\n        this.ssh.disconnect()\n    }\n\n    async openShellChannel (options: { x11: boolean }): Promise<russh.Channel> {\n        if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {\n            throw new Error('Cannot open shell channel before auth')\n        }\n        const ch = await this.ssh.activateChannel(await this.ssh.openSessionChannel())\n        await ch.requestPTY('xterm-256color', {\n            columns: 80,\n            rows: 24,\n            pixHeight: 0,\n            pixWidth: 0,\n        })\n        if (options.x11) {\n            await ch.requestX11Forwarding({\n                singleConnection: false,\n                authProtocol: 'MIT-MAGIC-COOKIE-1',\n                authCookie: crypto.randomBytes(16).toString('hex'),\n                screenNumber: 0,\n            })\n        }\n        if (this.profile.options.agentForward) {\n            await ch.requestAgentForwarding()","sourceCodeStart":839,"sourceCodeEnd":875,"githubUrl":"https://github.com/Eugeny/tabby/blob/14e2d60b9b6dee84a53c37f05eefeb803787de04/tabby-ssh/src/session/ssh.ts#L839-L875","documentation":"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.","triggerScenarios":"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.","commonSituations":"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().","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."],"exampleFix":"// before (shell.ts)\nthis.shell = await this.ssh.openShellChannel({ x11: this.profile.options.x11 })\n// after\nif (this.ssh.ssh instanceof russh.AuthenticatedSSHClient) {\n    this.shell = await this.ssh.openShellChannel({ x11: this.profile.options.x11 })\n} else {\n    throw new Error('SSH session not authenticated')\n}","handlingStrategy":"type-guard","validationCode":"import * as russh from 'russh'\nif (!(ssh.ssh instanceof russh.AuthenticatedSSHClient)) {\n    throw new Error('Cannot open shell: SSH session not authenticated yet')\n}","typeGuard":"import * as russh from 'russh'\nfunction isOpen (s: unknown): s is russh.AuthenticatedSSHClient {\n    return s instanceof russh.AuthenticatedSSHClient\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["ssh","shell","pty","russh","state"],"backgroundTag":null,"analyzedSha":"14e2d60b9b6dee84a53c37f05eefeb803787de04","analyzedAt":"2026-08-12T11:46:48.773Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}