chenhg5/cc-connect · error
session is closed
Error message
session is closed
What it means
agent/pi/session.go:327 — piSession.Send checks s.alive before doing any work; if the session has already been closed or the RPC process has died, it returns the plain error "session is closed". This guards against writing to a dead process's stdin after Close() or after the read loop observed process exit.
Source
Thrown at agent/pi/session.go:327
select {
case s.events <- evt:
case <-s.ctx.Done():
}
}
s.alive.Store(false)
}
// ── Send ─────────────────────────────────────────────────────
// Send writes a prompt to the Pi agent.
// In json mode (default): spawns a one-shot `pi --mode json` process.
// In rpc mode: writes a "prompt" command to the persistent RPC process stdin.
func (s *piSession) Send(msg string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {
s.sendWg.Add(1)
defer s.sendWg.Done()
if !s.alive.Load() {
return fmt.Errorf("session is closed")
}
attachDir := s.attachDir
if safeMessageID := sanitizePiAttachmentName(messageID); safeMessageID != "" {
attachDir = filepath.Join(attachDir, safeMessageID)
}
cleanAttachments(attachDir)
// Issue #1723: images are passed via pi's @<path> argv / message-text
// mechanism. pi's processImage loads them as visual inputs and the
// bytes never enter our prompt text.
//
// Issue #1767: non-image files are NOT passed via @<path>. pi's
// processFileArguments reads every @<path> file's full UTF-8 contents
// and inlines them into the prompt the model sees. For a >~1MB text
// attachment this can blow the model's context and trigger a 400 from
// the provider. Mirror the claudecode behaviour: save non-image files
// to disk and tell the model where they live via a plain pathView on GitHub (pinned to 4000b2338a)
Solutions
- Create a new session (e.g. /new or the platform's session-creation command) and resend the message.
- If using /switch, ensure the target session id exists and is alive; a closed session id cannot accept prompts.
- Check daemon logs for why the session died (preceding "pi: ..." stderr events) and fix that root cause.
- In calling code, treat this as terminal: do not retry on the same session object; recreate via the agent's StartSession.
Example fix
// before
err := session.Send(msg, id, nil, nil) // session already stopped
// after
if !sessionAlive(session) {
session, err = agent.StartSession(ctx, sessionID, workDir)
}
err = session.Send(msg, id, nil, nil) Defensive patterns
Strategy: validation
Validate before calling
func ensureAlive(sess *piSession) error {
if sess == nil || !sess.alive.Load() {
return errors.New("session is closed; create a new session before sending")
}
return nil
} Try / catch
if err := session.Send(msg, id, nil, nil); err != nil {
if strings.Contains(err.Error(), "session is closed") {
session, err = agent.StartSession(ctx, sessionID, workDir)
if err == nil {
err = session.Send(msg, id, nil, nil)
}
}
return err
} Prevention
- Always create/recreate the session before the first prompt after /stop, timeout, or crash.
- Serialize message delivery per session (don't queue sends onto a session being closed).
- Subscribe to the event stream and mark the session dead when the process-exit EventError arrives.
- Use the engine's session manager rather than holding raw session references across stop/switch operations.
When it happens
Trigger: Calling Send (directly or via the engine routing a platform message) when: (1) Close() was called on the session; (2) readLoopRPC finished (process exited) and set alive=false; (3) Send races with a concurrent Close from /stop or session timeout; (4) a queued message is delivered after the process crashed.
Common situations: User sends a message to a chat whose agent session was stopped with /stop or timed out; engine replays a queued message after pi crashed; a long-running turn outlived the session and the next prompt arrives post-exit.
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
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/acf683e5aaf5d81f.
Report an issue: GitHub.