{"record":{"id":"68e60e37cea7716e","repo":"chenhg5/cc-connect","slug":"acp-session-closed","errorCode":null,"errorMessage":"acp: session closed","messagePattern":"acp: session closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/acp/session.go","lineNumber":592,"sourceCode":"\t\t\tToolInputRaw: rawTool,\n\t\t\tSessionID:    s.currentACPSessionID(),\n\t\t})\n\t}()\n}\n\nfunc (s *acpSession) emit(ev core.Event) {\n\tif ev.SessionID == \"\" {\n\t\tev.SessionID = s.currentACPSessionID()\n\t}\n\tselect {\n\tcase s.events <- ev:\n\tcase <-s.ctx.Done():\n\t}\n}\n\nfunc (s *acpSession) Send(prompt string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {\n\tif !s.alive.Load() {\n\t\treturn fmt.Errorf(\"acp: session closed\")\n\t}\n\n\ts.sendMu.Lock()\n\tdefer s.sendMu.Unlock()\n\n\tfilePaths := core.SaveFilesToDisk(s.workDir, messageID, files)\n\tprompt = core.AppendFileRefs(prompt, filePaths)\n\tif len(images) > 0 {\n\t\tprompt = s.appendImageRefs(prompt, images)\n\t}\n\n\tsid := s.currentACPSessionID()\n\tif sid == \"\" {\n\t\treturn fmt.Errorf(\"acp: no agent session id\")\n\t}\n\n\tpromptBlocks := []any{\n\t\tmap[string]any{\"type\": \"text\", \"text\": prompt},","sourceCodeStart":574,"sourceCodeEnd":610,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/acp/session.go#L574-L610","documentation":"Sentinel error returned by acpSession.Send when the session's alive flag is false, i.e. the agent process has exited (or the session was closed) before the prompt could be sent. Callers get this instead of attempting a JSON-RPC call on a dead transport.","triggerScenarios":"Calling Send after the agent process exited (readLoop observed cmd.Wait return and stored alive=false), after Close() was called, or a race where the process dies between engine calls.","commonSituations":"Agent crashed mid-conversation (OOM, panic, fatal stderr); user ran /stop closing the session while a queued message was being sent; system shutdown cancelled s.ctx; idle timeout killed the agent.","solutions":["Check the process-exit log line (`acp: process exited`) and its stderr to find why the agent died before retrying.","Start a new session (the engine will spawn a fresh agent process) and resend the prompt.","Fix the underlying crash: raise memory limits, update the agent, or remove the offending prompt/flag.","Avoid sending on a stale session reference after Close/Stop; let the engine recreate the session via its normal path."],"exampleFix":"// before: caller keeps a dead session\nerr := sess.Send(prompt, id, nil, nil) // \"acp: session closed\"\n// after: recreate the session when it is no longer alive\nif !sessAlive(sess) {\n    sess, err = agent.StartSession(ctx, sessionID, nil)\n}\nerr = sess.Send(prompt, id, nil, nil)","handlingStrategy":"validation","validationCode":"func canSend(s core.AgentSession) bool {\n    type aliver interface{ IsAlive() bool }\n    a, ok := s.(aliver)\n    return !ok || a.IsAlive()\n}","typeGuard":"func sessionAlive(s *acpSession) bool { return s != nil && s.alive.Load() }","tryCatchPattern":"if err := sess.Send(prompt, mid, nil, nil); err != nil {\n    if strings.Contains(err.Error(), \"acp: session closed\") {\n        // recreate session and resend once\n        ns, serr := agent.StartSession(ctx, core.ContinueSession, nil)\n        if serr == nil { return ns.Send(prompt, mid, nil, nil) }\n    }\n    return err\n}","preventionTips":["Check session liveness before sending after any Stop/Close call.","Monitor agent process exits (the `acp: process exited` log) and recreate sessions proactively.","Avoid queueing sends to sessions during shutdown or idle-timeout windows."],"tags":["acp","session-lifecycle","agent-process","closed-session"],"backgroundTag":"session-already-closed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}