From 4f77062aedc0e387593fa6e5b18fbe8f47f7df56 Mon Sep 17 00:00:00 2001 From: Pritom14 Date: Sun, 31 May 2026 23:09:16 +0530 Subject: [PATCH] fix(terminal): clear conn entry before sending exited frame The exit callback enqueued the exited frame before deleting c.terms[id], so a client reopening on receipt of exited could hit the open guard while the entry was still set and have its open dropped. Delete first so the cleared entry is visible by the time the client sees exited. Co-Authored-By: Claude Opus 4.7 --- backend/internal/terminal/manager.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/internal/terminal/manager.go b/backend/internal/terminal/manager.go index 6f0133e3c..cda0a51fc 100644 --- a/backend/internal/terminal/manager.go +++ b/backend/internal/terminal/manager.go @@ -242,13 +242,15 @@ func (c *connState) openTerminal(_ context.Context, id string) { }) }, func() { - c.enqueue(serverMsg{Ch: chTerminal, ID: id, Type: msgExited}) - // Drop the connection's entry for this id when the pane exits so a - // later open is served instead of being dropped by the open guard. - // markExited fires this without s.mu held, so taking c.mu is safe. + // Clear the connection's entry for this id before sending exited so + // a client that reopens the moment it sees exited finds no stale + // entry and is served instead of dropped by the open guard. Ordering + // the delete after the frame would race that reopen. markExited fires + // this without s.mu held, so taking c.mu is safe. c.mu.Lock() delete(c.terms, id) c.mu.Unlock() + c.enqueue(serverMsg{Ch: chTerminal, ID: id, Type: msgExited}) }, ) // An already-exited session sent its exited frame from subscribe and has