chenhg5/cc-connect · warning
agent session became nil
Error message
agent session became nil
What it means
Sent through the sendDone channel when a captured agentSession reference is nil at the moment a queued prompt is about to be sent. Guards a race where cleanup nils the session between capture and use.
Source
Thrown at core/engine.go:3856
}
promptContent := e.buildSenderPrompt(msg.Content, msg.UserID, msg.UserName, msg.Platform, msg.SessionKey, msg.ChannelKey)
sendStart := time.Now()
state.mu.Lock()
state.currentMessageID = msg.MessageID
state.fromVoice = msg.FromVoice
state.sideText = ""
as := state.agentSession // capture under lock to avoid race with cleanup
state.mu.Unlock()
// Run Send concurrently with processInteractiveEvents. Some agents block inside
// Send until the prompt turn finishes (e.g. ACP session/prompt); they may emit
// EventPermissionRequest while blocked — the event loop must run in parallel.
sendDone := make(chan error, 1)
go func() {
if as == nil {
sendDone <- fmt.Errorf("agent session became nil")
return
}
sendDone <- as.Send(promptContent, msg.MessageID, msg.Images, msg.Files)
}()
e.processInteractiveEvents(state, session, sessions, interactiveKey, msg.MessageID, turnStart, stopTyping, sendDone, msg.ReplyCtx)
if elapsed := time.Since(sendStart); elapsed >= slowAgentSend {
slog.Warn("slow agent send", "elapsed", elapsed, "session", msg.SessionKey, "content_len", len(msg.Content))
}
stopTyping = nil // ownership transferred; prevent defer from double-stopping
// Start unsolicited reader and arm the idle close timer BEFORE draining
// queued messages. drainPendingMessages releases the session lock, and
// without this ordering the next user message can race in, call
// cancelAgentSessionIdleClose (a no-op since nothing was scheduled yet),
// and then the late schedule below arms a timer that no subsequent cancel
// will catch — closing the live session mid-turn. See #1686 P1-C P1-2.
// The schedule's own state checks (agentSession nil, stopped, etc.) andView on GitHub (pinned to 4000b2338a)
Solutions
- Retry the message after the session resets — a new session will be created
- Check ordering of /stop or /reset relative to message sends
- Add a nil check before scheduling sends
Example fix
// before
nextSend <- as.Send(queuedPrompt, ...)
// after
if as == nil { nextSend <- fmt.Errorf("agent session became nil"); return } Defensive patterns
Strategy: try-catch
Type guard
if as == nil { return fmt.Errorf("agent session became nil") } Try / catch
if err := <-sendDone; err != nil && strings.Contains(err.Error(), "became nil") { recreateSessionAndResend(prompt) } Prevention
- Serialize sends with session lifecycle transitions under the state mutex
- Always capture agentSession under lock and nil-check before use
- Test message-send vs /stop races in CI
When it happens
Trigger: state.agentSession captured under lock was already nil (cleanup ran first) while a prompt send goroutine starts.
Common situations: User sends a message at the same moment the session is being stopped/reset; race between cleanup and the send path.
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
- iflowSession: resolve session dir: %w
- session not found: %s
- session is closed
- session is closed
- session is closed
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/e2e907688c4d2200.
Report an issue: GitHub.