chenhg5/cc-connect · error
session is closed
Error message
session is closed
What it means
kimiSession.Send checks an atomic `alive` flag before doing anything; if the underlying kimi CLI process has already exited (or the session was closed/Stop()ed), Send refuses to accept the prompt and returns `session is closed`. A kimiSession's lifetime is exactly the lifetime of its child process, so a dead process means the session can no longer accept prompts. This is thrown by cc-connect's agent/kimi adapter, not by the kimi CLI itself.
Source
Thrown at agent/kimi/session.go:124
args = append(args, "-r", sid)
} else {
args = append(args, "--resume", sid)
}
}
if ks.model != "" {
args = append(args, "--model", ks.model)
}
if ks.workDir != "" && ks.flagSupport.WorkDir {
args = append(args, "--work-dir", ks.workDir)
}
args = append(args, "--prompt", prompt)
return args
}
func (ks *kimiSession) Send(prompt string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {
if !ks.alive.Load() {
return fmt.Errorf("session is closed")
}
// Save images and files into the workspace so Kimi CLI can access them.
attachDir := filepath.Join(ks.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
- Do not cache AgentSession handles across turns; obtain the current session from the engine/SessionManager before each Send.
- On 'session is closed', create a fresh session (StartSession with the same workDir) and resend the prompt.
- Check kimi CLI health and logs — an early process exit is the root cause; this error is only the symptom.
- If you manage lifecycle yourself, guard Send/Close concurrency and only Close() from one code path.
Example fix
// before
if err := sess.Send(prompt, msgID, nil, nil); err != nil {
return err // "session is closed" propagates to the user
}
// after
if err := sess.Send(prompt, msgID, nil, nil); err != nil {
if strings.Contains(err.Error(), "session is closed") {
sess, err = agent.StartSession(ctx, opts) // recreate dead session
if err != nil {
return err
}
return sess.Send(prompt, msgID, nil, nil)
}
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
// Go returns errors, not throws; pre-check via session info if exposed
// e.g. only send to sessions returned by an up-to-date ListSessions call
info, err := agent.ListSessions(ctx)
if err == nil && !containsID(info, currentID) {
sess, err = agent.StartSession(ctx, opts) // stale session, recreate first
} Type guard
func isSessionClosed(err error) bool {
return err != nil && strings.Contains(err.Error(), "session is closed")
} Try / catch
if err := sess.Send(prompt, msgID, nil, nil); err != nil {
if isSessionClosed(err) {
sess, err = agent.StartSession(ctx, opts)
if err != nil { return err }
err = sess.Send(prompt, msgID, nil, nil)
}
if err != nil { return err }
} Prevention
- Never cache AgentSession handles across turns; resolve the current session each time
- Consume the session's Events() channel so process-death EventErrors are observed promptly
- Monitor kimi CLI crashes (stderr/OOM) which are the usual root cause of dead sessions
- Serialize lifecycle operations (Start/Stop/Send) to avoid races
When it happens
Trigger: Calling Send() on a kimiSession after: (1) the kimi CLI process exited (crash, OOM kill, or self-termination), (2) Close()/Stop() was called on the session, (3) the session context was cancelled which kills the process and flips alive=false, or (4) a cached session handle is reused after the process already ran to completion.
Common situations: A long-running chat where the kimi binary died silently between turns; reusing a stored AgentSession reference after a /new or /switch; a timer/cron job sending into a session that was concurrently torn down; a broken or outdated kimi CLI that exits immediately at startup.
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
- session is closed
- session process is not running
- write stdin: %w
- iflowSession: resolve session dir: %w
- session not found: %s
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/da06f19eab3aa152.
Report an issue: GitHub.