chenhg5/cc-connect · error
session is closed
Error message
session is closed
What it means
geminiSession.Send checks the session's atomic `alive` flag before doing anything; if the session has been closed or its process exited, it rejects the prompt with `session is closed`. This prevents sending into a dead process and surfaces stale-session usage early.
Source
Thrown at agent/gemini/session.go:71
mode: mode,
timeout: timeout,
extraEnv: extraEnv,
events: make(chan core.Event, 64),
ctx: sessionCtx,
cancel: cancel,
}
gs.alive.Store(true)
if resumeID != "" && resumeID != core.ContinueSession {
gs.chatID.Store(resumeID)
}
return gs, nil
}
func (gs *geminiSession) Send(prompt string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) (err error) {
if !gs.alive.Load() {
return fmt.Errorf("session is closed")
}
// Save images and files into the workspace so Gemini CLI tools can access them.
attachDir := filepath.Join(gs.workDir, ".cc-connect", "attachments")
if (len(images) > 0 || len(files) > 0) && os.MkdirAll(attachDir, 0o755) != nil {
attachDir = os.TempDir()
}
var imageRefs []string
for i, img := range images {
ext := ".png"
switch img.MimeType {
case "image/jpeg":
ext = ".jpg"
case "image/gif":
ext = ".gif"
case "image/webp":
ext = ".webp"View on GitHub (pinned to 4000b2338a)
Solutions
- Check session liveness/events before sending, or catch this error and start a new session
- Inspect logs for why the previous process exited (timeout, crash, stderr output)
- Increase timeoutMins in the gemini agent config if sessions expire too early
- Implement automatic session recreation on this error in the calling layer
Example fix
// before
err := sess.Send(prompt, msgID, nil, nil) // fails: session is closed
// after
if err := sess.Send(prompt, msgID, nil, nil); err != nil && strings.Contains(err.Error(), "session is closed") {
sess, err = agent.StartSession(ctx, workDir, model)
if err == nil { err = sess.Send(prompt, msgID, nil, nil) }
} Defensive patterns
Strategy: validation
Validate before calling
// track liveness via events before sending
closed := false
go func() { for evt := range sess.Events() { if evt.Type == core.EventError || evt.Type == core.EventExit { closed = true } } }()
if closed { sess, _ = agent.StartSession(ctx, workDir, model) } Try / catch
if err := sess.Send(prompt, id, nil, nil); err != nil && err.Error() == "session is closed" {
sess, serr := agent.StartSession(ctx, workDir, model)
if serr != nil { return serr }
return sess.Send(prompt, id, nil, nil)
} Prevention
- Watch the session's Events channel for exit/error to drop stale references
- Recreate sessions proactively after timeouts or CLI crashes
- Set a generous timeoutMins in config for long tasks
When it happens
Trigger: Sending a prompt on a geminiSession after the CLI process exited (crash, timeout, natural completion), after an explicit close/Stop, or after the context was cancelled — any state where alive.Load() is false.
Common situations: Engine keeps a session reference after the gemini CLI timed out; user resumes a session that hit the configured timeout; crash of the CLI marked the session dead but the UI still routes messages to it.
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
- codex app-server connection is closed
- session file not found: %s
- session is closed
- acp: session/new: %w
- acp: session/new: empty sessionId
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/3fb14ba88af8a725.
Report an issue: GitHub.