chenhg5/cc-connect · error

session is closed

Error message

session is closed

What it means

Send in agent/qoder/session.go rejects any message when the session's atomic alive flag is false, returning the fixed error "session is closed". The qoder session is one-shot: Send starts a new qoder process per message, and once Close() runs (or the session was never kept alive) further sends are invalid state transitions.

Source

Thrown at agent/qoder/session.go:92

	}

	if resumeID != "" && resumeID != core.ContinueSession {
		qs.sessionID.Store(resumeID)
	}

	return qs, nil
}

func (qs *qoderSession) Send(prompt string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {
	if len(images) > 0 {
		slog.Warn("qoderSession: images not supported, ignoring")
	}
	if len(files) > 0 {
		filePaths := core.SaveFilesToDisk(qs.workDir, messageID, files)
		prompt = core.AppendFileRefs(prompt, filePaths)
	}
	if !qs.alive.Load() {
		return fmt.Errorf("session is closed")
	}

	args := append(append([]string{}, qs.extraArgs...), "-p", prompt, "-f", "stream-json", "-q", "-w", qs.workDir)

	sid := qs.CurrentSessionID()
	if sid != "" {
		args = append(args, "-r", sid)
	}

	if qs.mode == "yolo" {
		if os.Geteuid() == 0 {
			slog.Warn("qoderSession: --dangerously-skip-permissions not allowed under root, skipping flag")
		} else {
			args = append(args, "--dangerously-skip-permissions")
		}
	}

	if qs.model != "" {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check sess.Alive() before calling Send and create a new session if false
  2. Ensure the engine creates a fresh session after Close instead of reusing the old one
  3. Avoid racing Send with Close; serialize lifecycle transitions
  4. Log the lifecycle (Close callers) to find who closed the session early

Example fix

// before
err := session.Send(ctx, msg)
// after
if !session.Alive() {
    session, err = newQoderSession(...)
}
err = session.Send(ctx, msg)
Defensive patterns

Strategy: validation

Validate before calling

if !qs.Alive() {
    qs, err = newQoderSession(...) // recreate before sending
}

Try / catch

if err := session.Send(ctx, msg); err != nil {
    if err.Error() == "session is closed" {
        session = recreateSession()
        return session.Send(ctx, msg)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Send after Close() has been called on the session; sending on a session whose Close was triggered by teardown/cancellation; engine routing a queued message to an already-closed session.

Common situations: Message queued by the platform while the user ran /new or the session was stopped; a long-running turn completed and the session was closed before a follow-up message arrived; double-close then reuse.

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/8a8fca2913c6e210. Report an issue: GitHub.