chenhg5/cc-connect · error

session is closed

Error message

session is closed

What it means

opencode's session Send stages any images, then checks an atomic `alive` flag; if the underlying opencode session process has exited or the session was closed, Send returns `session is closed` without invoking the CLI. Like the kimi adapter, a session is only usable while its process is alive, and this error marks use of a stale handle.

Source

Thrown at agent/opencode/session.go:78

	if resumeID != "" && resumeID != core.ContinueSession {
		s.chatID.Store(resumeID)
	}

	return s, nil
}

func (s *opencodeSession) Send(prompt string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {
	if len(files) > 0 {
		filePaths := core.SaveFilesToDisk(s.workDir, messageID, files)
		prompt = core.AppendFileRefs(prompt, filePaths)
	}
	prompt, imagePaths, err := s.stageImages(prompt, images)
	if err != nil {
		return err
	}
	if !s.alive.Load() {
		return fmt.Errorf("session is closed")
	}

	s.resultSent.Store(false)
	s.expectingContinue.Store(false)

	chatID := s.CurrentSessionID()
	isResume := chatID != ""

	args := s.buildRunArgs(prompt, imagePaths, chatID)

	slog.Debug("opencodeSession: launching", "resume", isResume, "args", core.RedactArgs(args))

	cmd := exec.CommandContext(s.ctx, s.cmd, args...)
	cmd.Dir = s.workDir
	env := os.Environ()
	if len(s.extraEnv) > 0 {
		env = core.MergeEnv(env, s.extraEnv)
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Re-acquire the live session via the engine/SessionManager instead of holding a long-lived reference; on this error, start a new session and resend.
  2. Investigate why the process exited: the adapter emits an EventError with stderr before dying — consume Events() to capture the cause.
  3. Add retry logic that distinguishes 'session is closed' (recreate session) from transient send failures (retry same session).
  4. Avoid calling Stop()/Close() concurrently with Send; serialize lifecycle operations.

Example fix

// before
if err := s.Send(prompt, msgID, nil, nil); err != nil {
    return err
}

// after
if err := s.Send(prompt, msgID, nil, nil); err != nil {
    if strings.Contains(err.Error(), "session is closed") {
        ns, serr := agent.StartSession(ctx, opts)
        if serr != nil {
            return serr
        }
        return ns.Send(prompt, msgID, nil, nil)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// keep a fresh view of live sessions before sending
infos, err := agent.ListSessions(ctx)
if err == nil && !containsID(infos, s.CurrentSessionID()) {
    ns, err := agent.StartSession(ctx, opts) // stale: recreate first
    if err != nil { return err }
    s = ns
}

Type guard

func isSessionClosed(err error) bool {
    return err != nil && strings.Contains(err.Error(), "session is closed")
}

Try / catch

if err := s.Send(prompt, msgID, nil, nil); err != nil {
    if isSessionClosed(err) {
        ns, serr := agent.StartSession(ctx, opts)
        if serr != nil { return serr }
        return ns.Send(prompt, msgID, nil, nil)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Send() after: the opencode session process terminated (crash, completion, cancellation of its context); Close() already ran; the session was closed server-side and the adapter detected the exit; reusing a saved session reference across engine session switches.

Common situations: Messaging the bot after the coding session ended; the opencode CLI crashing between turns (check stderr/logs); resuming a stale session id after /new; concurrent Stop() from a timeout watchdog racing an in-flight prompt.

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