chenhg5/cc-connect · error
acp: session closed
Error message
acp: session closed
What it means
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.
Source
Thrown at agent/acp/session.go:592
ToolInputRaw: rawTool,
SessionID: s.currentACPSessionID(),
})
}()
}
func (s *acpSession) emit(ev core.Event) {
if ev.SessionID == "" {
ev.SessionID = s.currentACPSessionID()
}
select {
case s.events <- ev:
case <-s.ctx.Done():
}
}
func (s *acpSession) Send(prompt string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {
if !s.alive.Load() {
return fmt.Errorf("acp: session closed")
}
s.sendMu.Lock()
defer s.sendMu.Unlock()
filePaths := core.SaveFilesToDisk(s.workDir, messageID, files)
prompt = core.AppendFileRefs(prompt, filePaths)
if len(images) > 0 {
prompt = s.appendImageRefs(prompt, images)
}
sid := s.currentACPSessionID()
if sid == "" {
return fmt.Errorf("acp: no agent session id")
}
promptBlocks := []any{
map[string]any{"type": "text", "text": prompt},View on GitHub (pinned to 4000b2338a)
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.
Example fix
// before: caller keeps a dead session
err := sess.Send(prompt, id, nil, nil) // "acp: session closed"
// after: recreate the session when it is no longer alive
if !sessAlive(sess) {
sess, err = agent.StartSession(ctx, sessionID, nil)
}
err = sess.Send(prompt, id, nil, nil) Defensive patterns
Strategy: validation
Validate before calling
func canSend(s core.AgentSession) bool {
type aliver interface{ IsAlive() bool }
a, ok := s.(aliver)
return !ok || a.IsAlive()
} Type guard
func sessionAlive(s *acpSession) bool { return s != nil && s.alive.Load() } Try / catch
if err := sess.Send(prompt, mid, nil, nil); err != nil {
if strings.Contains(err.Error(), "acp: session closed") {
// recreate session and resend once
ns, serr := agent.StartSession(ctx, core.ContinueSession, nil)
if serr == nil { return ns.Send(prompt, mid, nil, nil) }
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- acp: initialize: %w
- acp: session/prompt: %w
- session is closed
- acp: agent option "cmd" or "command" is required (path or na
- acp: command %q not found in PATH: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/68e60e37cea7716e.
Report an issue: GitHub.