{"record":{"id":"d9dbb013cbc91608","repo":"can1357/oh-my-pi","slug":"agent-is-already-processing-use-steer-or-follow","errorCode":null,"errorMessage":"Agent is already processing. Use steer() or followUp() to queue messages, or wait for completion.","messagePattern":"Agent is already processing\\. Use steer\\(\\) or followUp\\(\\) to queue messages, or wait for completion\\.","errorType":"exception","errorClass":"AgentBusyError","httpStatus":null,"severity":"error","filePath":"packages/agent/src/agent.ts","lineNumber":1218,"sourceCode":"\t\tif (this.#abortController) signals.push(this.#abortController.signal);\n\t\tif (signal) signals.push(signal);\n\t\tif (this.#deadline !== undefined) {\n\t\t\tconst delay = this.#deadline - Date.now();\n\t\t\tif (delay <= 0) {\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tcontroller.abort(new DOMException(\"Deadline exceeded\", \"TimeoutError\"));\n\t\t\t\tsignals.push(controller.signal);\n\t\t\t} else {\n\t\t\t\tsignals.push(AbortSignal.timeout(delay));\n\t\t\t}\n\t\t}\n\t\tif (signals.length === 0) return undefined;\n\t\treturn signals.length === 1 ? signals[0] : AbortSignal.any(signals);\n\t}\n\n\tasync continue(signal?: AbortSignal) {\n\t\tif (this.#state.isStreaming) {\n\t\t\tthrow new AgentBusyError();\n\t\t}\n\n\t\tconst { promise, resolve } = Promise.withResolvers<void>();\n\t\tthis.#runningPrompt = promise;\n\t\tthis.#resolveRunningPrompt = resolve;\n\t\tconst continuationAbortController = new AbortController();\n\t\tthis.#abortController = continuationAbortController;\n\t\tthis.#state.isStreaming = true;\n\t\tthis.#state.streamMessage = null;\n\t\tthis.#state.error = undefined;\n\n\t\ttry {\n\t\t\tconst dequeueSignal = this.#continuationDequeueSignal(signal);\n\t\t\tconst messages = this.#state.messages;\n\t\t\tif (messages.length === 0) {\n\t\t\t\t// An empty transcript has nothing to resume, but a queued steer/follow-up\n\t\t\t\t// must still be delivered as the opening turn — mirroring the assistant-tail\n\t\t\t\t// branch below. Throwing here leaves the message undeliverable, and idle-drain","sourceCodeStart":1200,"sourceCodeEnd":1236,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/agent/src/agent.ts#L1200-L1236","documentation":"Agent.continue() throws AgentBusyError when this.#state.isStreaming is true, meaning a prompt/continuation loop is currently running (packages/agent/src/agent.ts:1216-1219). The Agent serializes runs: only one turn loop may be active at a time. The error message directs you to steer() or followUp() to inject messages into the running loop, or to await completion.","triggerScenarios":"Calling agent.continue() (or prompt()) while a previous prompt()/continue() run has not finished — isStreaming is still true. Common with concurrent callers: an event handler and a retry timer both invoking continue(), or calling continue() inside a streaming event callback.","commonSituations":"Firing continue() on a timer or on every streamed event without checking idle state; retry logic that re-invokes continue() after an error while the original loop is still tearing down; UI button double-clicks; multiple async tasks sharing one Agent instance.","solutions":["Queue the message instead: use steer() (mid-run steering) or followUp() (next turn) rather than starting a new run.","Await agent.waitForIdle() (or the promise returned by the in-flight prompt) before calling continue().","Track run state at the call site with a mutex/queue so only one caller drives the Agent at a time.","If you believe the agent is stuck, call agent.abort() first, wait for idle, then continue()."],"exampleFix":"// before\nawait agent.continue(); // throws AgentBusyError if a run is active\n\n// after\nawait agent.waitForIdle();\n// or, without waiting:\nagent.followUp(\"take another look\"); // queued into the running loop","handlingStrategy":"try-catch","validationCode":"function isAgentIdle(agent: Agent): boolean {\n  return !isStreaming(agent);\n}\nif (isAgentIdle(agent)) await agent.continue();","typeGuard":null,"tryCatchPattern":"try {\n  await agent.continue();\n} catch (err) {\n  if (err instanceof AgentBusyError) {\n    agent.followUp(pendingMessage); // queue instead of racing\n    return;\n  }\n  throw err;\n}","preventionTips":["Designate a single owner (queue/mutex) that drives prompt/continue for the Agent instance.","Await waitForIdle() before any new run; never call continue() from streaming event callbacks.","Use steer()/followUp() to inject messages into a live run instead of starting parallel runs.","Debounce UI triggers (buttons, timers) that can fire continue() while a run is active."],"tags":["concurrency","agent","busy-state","queueing"],"backgroundTag":"agent-already-processing","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}