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

  1. Do not cache AgentSession handles across turns; obtain the current session from the engine/SessionManager before each Send.
  2. On 'session is closed', create a fresh session (StartSession with the same workDir) and resend the prompt.
  3. Check kimi CLI health and logs — an early process exit is the root cause; this error is only the symptom.
  4. 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

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/da06f19eab3aa152. Report an issue: GitHub.