{"record":{"id":"acf683e5aaf5d81f","repo":"chenhg5/cc-connect","slug":"session-is-closed-acf683","errorCode":null,"errorMessage":"session is closed","messagePattern":"session is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/pi/session.go","lineNumber":327,"sourceCode":"\t\tselect {\n\t\tcase s.events <- evt:\n\t\tcase <-s.ctx.Done():\n\t\t}\n\t}\n\ts.alive.Store(false)\n}\n\n// ── Send ─────────────────────────────────────────────────────\n\n// Send writes a prompt to the Pi agent.\n// In json mode (default): spawns a one-shot `pi --mode json` process.\n// In rpc mode: writes a \"prompt\" command to the persistent RPC process stdin.\nfunc (s *piSession) Send(msg string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {\n\ts.sendWg.Add(1)\n\tdefer s.sendWg.Done()\n\n\tif !s.alive.Load() {\n\t\treturn fmt.Errorf(\"session is closed\")\n\t}\n\n\tattachDir := s.attachDir\n\tif safeMessageID := sanitizePiAttachmentName(messageID); safeMessageID != \"\" {\n\t\tattachDir = filepath.Join(attachDir, safeMessageID)\n\t}\n\tcleanAttachments(attachDir)\n\n\t// Issue #1723: images are passed via pi's @<path> argv / message-text\n\t// mechanism. pi's processImage loads them as visual inputs and the\n\t// bytes never enter our prompt text.\n\t//\n\t// Issue #1767: non-image files are NOT passed via @<path>. pi's\n\t// processFileArguments reads every @<path> file's full UTF-8 contents\n\t// and inlines them into the prompt the model sees. For a >~1MB text\n\t// attachment this can blow the model's context and trigger a 400 from\n\t// the provider. Mirror the claudecode behaviour: save non-image files\n\t// to disk and tell the model where they live via a plain path","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/pi/session.go#L309-L345","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nerr := session.Send(msg, id, nil, nil) // session already stopped\n// after\nif !sessionAlive(session) {\n    session, err = agent.StartSession(ctx, sessionID, workDir)\n}\nerr = session.Send(msg, id, nil, nil)","handlingStrategy":"validation","validationCode":"func ensureAlive(sess *piSession) error {\n    if sess == nil || !sess.alive.Load() {\n        return errors.New(\"session is closed; create a new session before sending\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := session.Send(msg, id, nil, nil); err != nil {\n    if strings.Contains(err.Error(), \"session is closed\") {\n        session, err = agent.StartSession(ctx, sessionID, workDir)\n        if err == nil {\n            err = session.Send(msg, id, nil, nil)\n        }\n    }\n    return err\n}","preventionTips":["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."],"tags":["session-lifecycle","closed-session","go","agent-pi"],"backgroundTag":"invalid-state-transition","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}