Skip to content

Commit 41043ee

Browse files
committed
fix: guard handleClientMessage against resize on exited PTY
pty.resize() throws EBADF after the PTY process exits. A resize message arriving after exit but before client cleanup propagates the throw through the Hono WS handler, producing noisy errors. Add this.exited early-return for input and resize cases, matching the guard pattern already used in addClient(). ping remains unguarded — it's pure WS with no PTY involvement.
1 parent 661528c commit 41043ee

2 files changed

Lines changed: 18 additions & 0 deletions

File tree

src/session.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,12 @@ export class SharedTerminalSession {
145145
handleClientMessage(client: SessionClient, message: ClientMessage): void {
146146
switch (message.type) {
147147
case 'input':
148+
if (this.exited) return
148149
this.pty.write(message.data)
149150
return
150151

151152
case 'resize':
153+
if (this.exited) return
152154
this.pty.resize(message.cols, message.rows)
153155
this.mirror.resize(message.cols, message.rows)
154156
return

tests/session.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,22 @@ describe('SharedTerminalSession', () => {
5959
expect(recorder.getCloseCount()).toBe(1)
6060
})
6161

62+
test('handleClientMessage silently ignores input and resize after PTY exit', async () => {
63+
const session = new SharedTerminalSession(['bash', '--norc', '--noprofile', '-lc', 'exit 0'])
64+
65+
await session.onExit
66+
67+
const recorder = createClientRecorder()
68+
69+
// pty.resize() throws EBADF after exit — these must not throw
70+
session.handleClientMessage(recorder.client, { type: 'input', data: 'hello' })
71+
session.handleClientMessage(recorder.client, { type: 'resize', cols: 120, rows: 40 })
72+
73+
// ping should still work — pure WS, no PTY involvement
74+
session.handleClientMessage(recorder.client, { type: 'ping' })
75+
expect(recorder.getMessages()).toEqual([{ type: 'pong' }])
76+
})
77+
6278
test('late clients receive the final snapshot and exit after the PTY is gone', async () => {
6379
const session = new SharedTerminalSession([
6480
'bash',

0 commit comments

Comments
 (0)