{"record":{"id":"930031f1a755dba0","repo":"chenhg5/cc-connect","slug":"iflow-session-is-busy","errorCode":null,"errorMessage":"iflow session is busy","messagePattern":"iflow session is busy","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"agent/iflow/session.go","lineNumber":133,"sourceCode":"\t\ts.sentOnce.Store(true)\n\t}\n\n\treturn s, nil\n}\n\nfunc (s *iflowSession) Send(prompt string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {\n\tif len(images) > 0 {\n\t\tslog.Warn(\"iflowSession: images are not supported, ignoring\")\n\t}\n\tif len(files) > 0 {\n\t\tfilePaths := core.SaveFilesToDisk(s.workDir, messageID, files)\n\t\tprompt = core.AppendFileRefs(prompt, filePaths)\n\t}\n\tif !s.alive.Load() {\n\t\treturn fmt.Errorf(\"session is closed\")\n\t}\n\tif !s.turnActive.CompareAndSwap(false, true) {\n\t\treturn fmt.Errorf(\"iflow session is busy\")\n\t}\n\n\tturnCtx, turnCancel := context.WithCancel(s.ctx)\n\tturn := &iflowTurn{\n\t\tcancel:         turnCancel,\n\t\tstartedAt:      time.Now(),\n\t\tmode:           s.mode,\n\t\tpendingTimeout: s.pendingToolTimeout(),\n\t\tprocessDone:    make(chan struct{}),\n\t\tpendingToolIDs: make(map[string]struct{}),\n\t\tpendingTools:   make(map[string]iflowToolUse),\n\t\tseenToolIDs:    make(map[string]struct{}),\n\t\tdoneToolIDs:    make(map[string]struct{}),\n\t}\n\n\tdefer func() {\n\t\tif !s.turnActive.Load() {\n\t\t\tturnCancel()","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/iflow/session.go#L115-L151","documentation":"iflowSession.Send returns this when a turn is already active: `turnActive.CompareAndSwap(false, true)` failed because a previous prompt is still being processed. iflow sessions process one turn at a time, so concurrent Send calls are rejected.","triggerScenarios":"Two Send() calls on the same iflow AgentSession while the first turn is still streaming (e.g. user sends a second message before the agent finishes, or a retry timer fires mid-turn).","commonSituations":"User sends rapid follow-up messages in the chat platform; a queued/retry path re-sends a prompt that is already running; a cron/timer job overlaps an interactive session turn.","solutions":["Wait for the current turn to finish before sending again; queue messages at the engine level","Check busy state before calling Send (if the session exposes one) and surface a 'busy, please wait' message to the user","Cancel the active turn if the new message should preempt it, then resend","Use a separate session for concurrent workloads instead of sharing one"],"exampleFix":"// before\nif err := session.Send(ctx, msg, nil); err != nil { return err }\n// after\nif err := session.Send(ctx, msg, nil); err != nil {\n    if strings.Contains(err.Error(), \"session is busy\") {\n        busyQueue <- msg // enqueue until current turn completes\n        return nil\n    }\n    return err\n}","handlingStrategy":"validation","validationCode":"if session.IsBusy() {\n    return enqueueForLater(session, msg)\n}","typeGuard":null,"tryCatchPattern":"if err := session.Send(ctx, msg, nil); err != nil {\n    if strings.Contains(err.Error(), \"session is busy\") {\n        return queueMessage(session, msg) // retry after turn completes\n    }\n    return err\n}","preventionTips":["Serialize Send calls per session with a mutex or message queue","Surface a 'busy' status to users instead of failing silently","Debounce rapid user messages in the platform adapter before dispatching"],"tags":["go","concurrency","session-busy","iflow"],"backgroundTag":"invalid-state-transition","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"}