chenhg5/cc-connect · error

session is closed

Error message

session is closed

What it means

iflowSession.Send returns this when the session's `alive` atomic flag is false, meaning the session has already been closed/stopped and cannot accept new prompts. The library throws it as a guard so callers get a clear error instead of writing to a dead PTY process.

Source

Thrown at agent/iflow/session.go:130

	if resumeID != "" && resumeID != core.ContinueSession {
		s.sessionID.Store(resumeID)
		s.sentOnce.Store(true)
	}

	return s, nil
}

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

	turnCtx, turnCancel := context.WithCancel(s.ctx)
	turn := &iflowTurn{
		cancel:         turnCancel,
		startedAt:      time.Now(),
		mode:           s.mode,
		pendingTimeout: s.pendingToolTimeout(),
		processDone:    make(chan struct{}),
		pendingToolIDs: make(map[string]struct{}),
		pendingTools:   make(map[string]iflowToolUse),
		seenToolIDs:    make(map[string]struct{}),
		doneToolIDs:    make(map[string]struct{}),
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check session liveness (or handle the error) before Send and create a new session via Agent.StartSession when the old one is closed
  2. Ensure callers stop routing messages to a session after Close/Stop and remove it from the session registry
  3. If the session closed unexpectedly, inspect logs for the iflow process exit cause (see summarizeIFlowError output) and fix the underlying CLI failure
  4. Guard concurrent teardown with the session's mutex/once so Close is not invoked while a message is in flight

Example fix

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

Strategy: try-catch

Validate before calling

if session == nil || !session.IsAlive() {
    session, err = agent.StartSession(ctx, opts)
}

Type guard

func sessionUsable(s core.AgentSession, alive func() bool) bool { return s != nil && alive() }

Try / catch

if err := session.Send(ctx, msg, nil); err != nil {
    if strings.Contains(err.Error(), "session is closed") {
        session, err = agent.StartSession(ctx, opts)
        if err == nil { err = session.Send(ctx, msg, nil) }
    }
}

Prevention

When it happens

Trigger: Calling Send() on an AgentSession after Close()/Stop() was called, after the underlying iflow CLI process exited and the session was marked not alive, or after the session context was cancelled.

Common situations: Messaging-platform side keeps a stale session reference after the user ran /stop or the agent process crashed; engine routes a new user message to a session that already terminated; race where the process exits between selecting the session and sending.

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