{"record":{"id":"28a645cba935edd9","repo":"earendil-works/pi","slug":"agent-is-already-processing-wait-for-completion-b","errorCode":null,"errorMessage":"Agent is already processing. Wait for completion before resetting.","messagePattern":"Agent is already processing\\. Wait for completion before resetting\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/agent.ts","lineNumber":335,"sourceCode":"\n\t/** Abort the current run, if one is active. */\n\tabort(): void {\n\t\tthis.activeRun?.abortController.abort();\n\t}\n\n\t/**\n\t * Resolve when the current run and all awaited event listeners have finished.\n\t *\n\t * This resolves after `agent_end` listeners settle.\n\t */\n\twaitForIdle(): Promise<void> {\n\t\treturn this.activeRun?.promise ?? Promise.resolve();\n\t}\n\n\t/** Clear transcript state, runtime state, and queued messages. */\n\treset(): void {\n\t\tif (this.activeRun) {\n\t\t\tthrow new Error(\"Agent is already processing. Wait for completion before resetting.\");\n\t\t}\n\n\t\tthis._state.messages = [];\n\t\tthis._state.isStreaming = false;\n\t\tthis._state.streamingMessage = undefined;\n\t\tthis._state.pendingToolCalls = new Set<string>();\n\t\tthis._state.errorMessage = undefined;\n\t\tthis.clearFollowUpQueue();\n\t\tthis.clearSteeringQueue();\n\t}\n\n\t/** Start a new prompt from text, a single message, or a batch of messages. */\n\tasync prompt(message: AgentMessage | AgentMessage[]): Promise<void>;\n\tasync prompt(input: string, images?: ImageContent[]): Promise<void>;\n\tasync prompt(input: string | AgentMessage | AgentMessage[], images?: ImageContent[]): Promise<void> {\n\t\tif (this.activeRun) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.\",","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/earendil-works/pi/blob/4af9d21d3b4d664e4a29fcabfec85171077248e3/packages/agent/src/agent.ts#L317-L353","documentation":"Agent.reset() clears transcript state, runtime state, and both message queues in one step; it refuses to run while activeRun exists because the in-flight loop holds a snapshot of the context and would keep emitting events into wiped state. The guard is a synchronous throw, so when it fires nothing has been cleared. The agent stays 'active' until the run and all awaited agent_end listeners have settled, not merely until the stream closes.","triggerScenarios":"Calling agent.reset() while prompt() or continue() is still in flight; a UI 'new conversation' handler racing an in-progress turn; resetting in a listener that runs before agent_end listeners settle; calling abort() then reset() immediately without waiting for the abort to finish.","commonSituations":"Chat apps wiring a new-chat button to reset without awaiting the previous turn; tests resetting in afterEach while a slow stream drains; fire-and-forget prompt() calls that leave activeRun set longer than expected.","solutions":["await agent.waitForIdle() before reset() - it resolves immediately when idle and after agent_end listeners settle when busy.","If the run should be stopped, call agent.abort() first, then await agent.waitForIdle(), then reset().","In UIs, disable or defer the reset action while agent.state.isStreaming is true."],"exampleFix":"// before\nagent.abort();\nagent.reset(); // throws: run still active\n\n// after\nagent.abort();\nawait agent.waitForIdle();\nagent.reset();","handlingStrategy":"validation","validationCode":"if (agent.state.isStreaming) {\n  await agent.waitForIdle(); // resolves after agent_end listeners settle\n}\nagent.reset();","typeGuard":null,"tryCatchPattern":"try {\n  agent.reset(); // synchronous throw, nothing is cleared on failure\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"before resetting\")) {\n    agent.abort();\n    await agent.waitForIdle();\n    agent.reset(); // safe now\n  } else {\n    throw err;\n  }\n}","preventionTips":["Centralize reset behind one async newConversation() helper that awaits waitForIdle first.","After abort(), always await waitForIdle() before touching agent state.","Remember the agent stays active until agent_end listeners settle, not just until the stream closes."],"tags":["agent","lifecycle","reset","concurrency","race-condition"],"backgroundTag":"agent-busy-concurrent-call","analyzedSha":"4af9d21d3b4d664e4a29fcabfec85171077248e3","analyzedAt":"2026-08-24T13:07:14.692Z","schemaVersion":2},"datasetVersion":"2026-08-24T17:17:21.512Z"}