chenhg5/cc-connect · error
tmux: session closed
Error message
tmux: session closed
What it means
tmuxSession.Send first checks the atomic alive flag; if the session has been closed (or was never fully started), it returns 'tmux: session closed' without touching tmux. The tmux agent models a persistent terminal, so a closed session is terminal — messages cannot be delivered to it.
Source
Thrown at agent/tmux/session.go:77
s := &tmuxSession{
target: target,
sessionID: sessionID,
workDir: workDir,
promptPat: pat,
pollInt: pollInt,
stripInputBlock: stripInputBlock,
stripPatterns: stripPats,
events: make(chan core.Event, 128),
ctx: sessCtx,
cancel: cancel,
}
s.alive.Store(true)
return s, nil
}
func (s *tmuxSession) Send(prompt string, messageID string, _ []core.ImageAttachment, files []core.FileAttachment) error {
if !s.alive.Load() {
return fmt.Errorf("tmux: session closed")
}
// Save attached files and append their paths to the prompt
if len(files) > 0 {
paths := core.SaveFilesToDisk(s.workDir, messageID, files)
if len(paths) > 0 {
prompt = prompt + "\n# files: " + strings.Join(paths, ", ")
}
}
// Cancel any running poll from a previous Send
s.mu.Lock()
if s.pollCancel != nil {
s.pollCancel()
s.pollCancel = nil
}
// Snapshot the full scrollback (history + visible pane) before sending.View on GitHub (pinned to 4000b2338a)
Solutions
- Verify session lifecycle: only send to the session returned by the current StartSession/lookup, not a stale reference
- Handle the error by prompting the user to start a new session (or auto-start one) instead of retrying Send on the closed object
- If the race is between teardown and in-flight messages, synchronize with the engine so messages are rejected/redirected before Stop() completes
Example fix
// caller side
if err := sess.Send(prompt, msgID, nil, files); err != nil {
if strings.Contains(err.Error(), "session closed") {
sess, err = agent.StartSession(ctx, opts) // recreate
}
} Defensive patterns
Strategy: type-guard
Validate before calling
// before sending, ensure the session is current
if sess == nil || sess.ID() != engine.CurrentSessionID(chatID) {
return fmt.Errorf("stale session reference")
} Type guard
func isSessionClosed(err error) bool {
return err != nil && strings.Contains(err.Error(), "tmux: session closed")
} Try / catch
// Go
if err := sess.Send(prompt, id, nil, files); isSessionClosed(err) {
newSess, serr := agent.StartSession(ctx, opts)
if serr == nil {
err = newSess.Send(prompt, id, nil, files)
}
} Prevention
- Fetch the session via the engine per message instead of caching references
- Stop accepting input for a chat as soon as its session is closed
- Add an alive/liveness query to session wrappers rather than probing via Send
When it happens
Trigger: Calling Send on a tmuxSession after Stop() was called, after the underlying context was canceled (agent shutdown, engine teardown, /new or /switch closing the session), or on a session whose creation path failed to set alive.
Common situations: Engine still routing a queued user message to a session the user just closed; a race between the platform handler and session teardown; reusing a stored session reference after a reconnect created a new session object.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- tmux: session %q does not exist and auto_create is disabled
- tmux: create window %q in session %q: %w
- acp: session closed
- codex app-server connection is closed
- session is closed
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/84e5ea5a29e1465a.
Report an issue: GitHub.