{"record":{"id":"4ed263fbf0e42af5","repo":"can1357/oh-my-pi","slug":"cannot-continue-from-message-role-assistant-4ed263","errorCode":null,"errorMessage":"Cannot continue from message role: assistant","messagePattern":"Cannot continue from message role: assistant","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/agent.ts","lineNumber":1274,"sourceCode":"\t\t\t\t// blocks and their results would break the provider's pairing\n\t\t\t\t// invariant. Queued messages drain inside the resumed loop instead.\n\t\t\t\tif (unpairedToolCallTail(messages)) {\n\t\t\t\t\tawait this.#runLoop(undefined, undefined, signal, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst queuedSteering = await this.#dequeueSteeringMessagesAfterHooks(dequeueSignal);\n\t\t\t\tif (queuedSteering.length > 0) {\n\t\t\t\t\tawait this.#runLoop(queuedSteering, { skipInitialSteeringPoll: true }, signal, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst queuedFollowUp = await this.#dequeueFollowUpMessagesAfterHooks(dequeueSignal);\n\t\t\t\tif (queuedFollowUp.length > 0) {\n\t\t\t\t\tawait this.#runLoop(queuedFollowUp, undefined, signal, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthrow new Error(\"Cannot continue from message role: assistant\");\n\t\t\t}\n\n\t\t\tawait this.#runLoop(undefined, undefined, signal, true);\n\t\t} finally {\n\t\t\tresolve();\n\t\t\tif (this.#abortController === continuationAbortController) {\n\t\t\t\tthis.#state.isStreaming = false;\n\t\t\t\tthis.#state.streamMessage = null;\n\t\t\t\tthis.#state.pendingToolCalls.clear();\n\t\t\t\tthis.#abortController = undefined;\n\t\t\t\tif (this.#runningPrompt === promise) {\n\t\t\t\t\tthis.#runningPrompt = undefined;\n\t\t\t\t\tthis.#resolveRunningPrompt = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n","sourceCodeStart":1256,"sourceCodeEnd":1292,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/agent/src/agent.ts#L1256-L1292","documentation":"Agent.continue() throws this when the last message in the transcript has role \"assistant\" but the tail contains no unpaired runnable tool calls and no steering/follow-up messages are queued (packages/agent/src/agent.ts:1274). A completed assistant turn is a natural stopping point: there is no pending tool result to feed back and no new user input, so the loop cannot decide what to generate next. Continuation is only valid from a user/tool tail or an assistant tail mid tool-execution.","triggerScenarios":"Calling continue() right after a normal assistant reply finished (turn completed cleanly); resuming a session whose transcript ends with a final assistant answer; an idle-drain loop re-invoking continue() after the agent already answered with nothing queued.","commonSituations":"Auto-continue/retry schedulers that keep calling continue() after every completed turn; loading a saved session that ended on a complete assistant message and attempting to resume it; confusion between 'continue the conversation' (use prompt/followUp) and 'resume interrupted work' (continue).","solutions":["Send a new user message with prompt() or queue one with followUp() instead of calling continue() — a finished assistant turn needs new input.","Check the last message role before continuing: only continue when the tail is a user/tool message or an assistant message with unpaired tool calls.","If your drain loop calls continue() repeatedly, stop when the turn completed and nothing is queued (the error signals that state).","If you expected tool calls to be pending, verify the run didn't already complete them before the interruption."],"exampleFix":"// before\nawait agent.continue(); // throws when last message is a final assistant reply\n\n// after\nconst msgs = getMessages(agent);\nconst last = msgs[msgs.length - 1];\nif (last?.role === \"assistant\") {\n  agent.followUp(\"please proceed\");\n} else {\n  await agent.continue();\n}","handlingStrategy":"validation","validationCode":"function continueIsMeaningful(agent: Agent): boolean {\n  const msgs = getMessages(agent);\n  const last = msgs[msgs.length - 1];\n  if (!last) return false;\n  if (last.role !== \"assistant\") return true;\n  return hasQueuedMessages(agent) || hasUnpairedToolCalls(msgs);\n}\nif (continueIsMeaningful(agent)) await agent.continue();","typeGuard":null,"tryCatchPattern":"try {\n  await agent.continue();\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Cannot continue from message role\")) {\n    agent.followUp(\"please proceed with the next step\");\n    return;\n  }\n  throw err;\n}","preventionTips":["Treat a completed assistant turn as an endpoint: send new input via prompt()/followUp(), not continue().","In auto-continue schedulers, stop the loop once the tail is a final assistant message with nothing queued.","Inspect the last message role before resuming persisted sessions.","Distinguish 'resume interrupted tool work' (continue) from 'continue the conversation' (prompt/followUp) in your API usage."],"tags":["state","agent","conversation-flow","api-misuse"],"backgroundTag":"invalid-continue-state","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}