{"record":{"id":"1501289f4e73c97f","repo":"chenhg5/cc-connect","slug":"agent-session-ended","errorCode":null,"errorMessage":"agent session ended","messagePattern":"agent session ended","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/engine.go","lineNumber":3289,"sourceCode":"// drainOrphanedQueue is called when a message was queued but the drain loop\n// has already exited. It processes all pending messages in the state, similar\n// to the drain loop in processInteractiveMessageWith but as a standalone\n// goroutine.\nfunc (e *Engine) drainOrphanedQueue(session *Session, sessions *SessionManager, interactiveKey string, agent Agent, workspaceDir string) {\n\tunlocked := false\n\tdefer func() {\n\t\tif !unlocked {\n\t\t\tsession.Unlock()\n\t\t}\n\t}()\n\n\te.interactiveMu.Lock()\n\tstate, hasState := e.interactiveStates[interactiveKey]\n\te.interactiveMu.Unlock()\n\n\tif !hasState || state == nil || state.agentSession == nil || !state.agentSession.Alive() {\n\t\tif hasState && state != nil {\n\t\t\te.notifyDroppedQueuedMessages(state, fmt.Errorf(\"agent session ended\"))\n\t\t}\n\t\treturn\n\t}\n\n\t// Stop unsolicited reader before draining — drainPendingMessages reads\n\t// from Events() and we must not have concurrent readers.\n\te.stopUnsolicitedReader(state)\n\n\tunlocked = e.drainPendingMessages(state, session, sessions, interactiveKey)\n\n\t// Restart unsolicited reader if the session is still alive and clean.\n\tstate.mu.Lock()\n\talive := state.agentSession != nil && state.agentSession.Alive() && !state.stopped && !state.eventsNeedResync\n\tstate.mu.Unlock()\n\tif alive {\n\t\te.startUnsolicitedReader(state, session, sessions, interactiveKey, workspaceDir)\n\t}\n}","sourceCodeStart":3271,"sourceCodeEnd":3307,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/engine.go#L3271-L3307","documentation":"Not a returned error but a reason string passed to notifyDroppedQueuedMessages when an interactive session's agent session is missing or no longer Alive() at drain time. All queued user messages for that session are dropped and the user is notified.","triggerScenarios":"Drain runs after the agent process exited/crashed or state.agentSession is nil; interactive state exists but its session is dead.","commonSituations":"Agent CLI crashed or was killed externally during a long prompt; session timed out and closed before queued messages were flushed.","solutions":["Check agent process logs for the crash/exit cause","Recreate the session (/new or send a fresh message) to spawn a new agent session","Reduce queue time by sending messages when the session is active","Monitor agent health to detect premature exits"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"if state == nil || state.agentSession == nil || !state.agentSession.Alive() { /* recreate session before enqueueing */ }","typeGuard":"alive := state != nil && state.agentSession != nil && state.agentSession.Alive()","tryCatchPattern":"engine.OnQueuedMessagesDropped(func(key string, reason error) { user.Notify(\"session ended; please resend\") })","preventionTips":["Monitor agent process liveness with doctor checks","Avoid queuing messages onto sessions showing signs of exit","Recreate sessions promptly after agent crashes"],"tags":["go","session","queue"],"backgroundTag":"process-exited-unexpectedly","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}