can1357/oh-my-pi · error
Cannot continue from message role: assistant
Error message
Cannot continue from message role: assistant
What it means
agentLoopContinue requires the last message to be awaiting a response. If the final message has role "assistant" — and it is not an unpaired tool-call tail that legitimately resumes pending tool calls — continuing would produce an assistant-to-assistant turn, which providers reject or which makes no protocol sense, so it throws. Exception: a trailing assistant message with unpaired tool calls is allowed so the loop can resume tool execution.
Source
Thrown at packages/agent/src/agent-loop.ts:599
*
* **Important:** The last message in context must convert to a `user` or `toolResult` message
* via `convertToLlm` — except for an assistant tail with unpaired runnable
* tool calls (see {@link unpairedToolCallTail}), which resumes by executing
* those calls first. Any other assistant tail is rejected here; other invalid
* tails cannot be validated since `convertToLlm` is only called once per turn.
*/
export function agentLoopContinue(
context: AgentContext,
config: AgentLoopConfig,
signal?: AbortSignal,
streamFn?: StreamFn,
): EventStream<AgentEvent, AgentMessage[]> {
if (context.messages.length === 0) {
throw new Error("Cannot continue: no messages in context");
}
if (context.messages[context.messages.length - 1].role === "assistant" && !unpairedToolCallTail(context.messages)) {
throw new Error("Cannot continue from message role: assistant");
}
const stream = createAgentStream();
(async () => {
const newMessages: AgentMessage[] = [];
const currentContext: AgentContext = { ...context, messages: [...context.messages] };
stream.push({ type: "agent_start" });
try {
await runLoop(currentContext, newMessages, config, signal, stream, streamFn);
} catch (err) {
stream.fail(err);
}
})();
return stream;View on GitHub (pinned to 9690622007)
Solutions
- Append a new user message (or tool results) before calling continue.
- If regenerating the last assistant reply, drop the trailing assistant message from context.messages first.
- If resuming pending tool calls, ensure the trailing assistant message keeps its unpaired tool calls so the unpairedToolCallTail check passes.
Example fix
// before
// last message is assistant; regenerate desired
await agentLoopContinue(context, config);
// after
if (context.messages.at(-1)?.role === "assistant") {
context.messages.pop(); // drop completed assistant turn
}
await agentLoopContinue(context, config); Defensive patterns
Strategy: validation
Validate before calling
const last = context.messages[context.messages.length - 1];
if (last?.role === "assistant" && !hasUnpairedToolCalls(last)) {
context.messages.pop(); // or append a user/tool message
} Type guard
function lastExpectsResponse(context: AgentContext): boolean {
const last = context.messages[context.messages.length - 1];
if (!last) return false;
return last.role === "user" || last.role === "toolResult" || hasUnpairedToolCalls(last);
} Try / catch
try {
return agentLoopContinue(context, config);
} catch (err) {
if (err instanceof Error && err.message.includes("Cannot continue from message role")) {
context.messages.pop();
return agentLoopContinue(context, config);
}
throw err;
} Prevention
- Call continue only when the last message is a user/tool message or an unpaired tool-call tail.
- For regenerate semantics, drop the trailing assistant message first.
- Keep tool-call pairing intact when serializing/restoring sessions.
When it happens
Trigger: Calling agentLoopContinue right after an assistant reply completed (last message role is "assistant", with no pending tool calls); manually appending an assistant message then calling continue.
Common situations: Resume/retry logic that captured the context after a finished turn instead of after the user reply; buggy session serialization that records the assistant turn last; caller intent to 'regenerate' implemented by calling continue instead of a regenerate API.
Related errors
- Cannot continue: no messages in context
- Replacement text is not valid UTF-8: {err}
- invalid glob `{pattern}`: {error}
- too many templates
- extra operand {} file operands cannot be combined with --fil
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d64d93a84f2c072b.
Report an issue: GitHub.