earendil-works/pi · error · Error
Cannot continue from message role: assistant
Error message
Cannot continue from message role: assistant
What it means
Agent.continue() requires the transcript to end with a user or toolResult message. When the last message is assistant, it first tries to rescue the call by draining queued steering messages, then queued follow-ups; only when both queues are empty does it throw. Hitting it means the agent already finished its answer and you asked it to continue with no new input - send a new prompt instead.
Source
Thrown at packages/agent/src/agent.ts:384
const lastMessage = this._state.messages[this._state.messages.length - 1];
if (!lastMessage) {
throw new Error("No messages to continue from");
}
if (lastMessage.role === "assistant") {
const queuedSteering = this.steeringQueue.drain();
if (queuedSteering.length > 0) {
await this.runPromptMessages(queuedSteering, { skipInitialSteeringPoll: true });
return;
}
const queuedFollowUps = this.followUpQueue.drain();
if (queuedFollowUps.length > 0) {
await this.runPromptMessages(queuedFollowUps);
return;
}
throw new Error("Cannot continue from message role: assistant");
}
await this.runContinuation();
}
private normalizePromptInput(
input: string | AgentMessage | AgentMessage[],
images?: ImageContent[],
): AgentMessage[] {
if (Array.isArray(input)) {
return input;
}
if (typeof input !== "string") {
return [input];
}
const content: Array<TextContent | ImageContent> = [{ type: "text", text: input }];View on GitHub (pinned to 4af9d21d3b)
Solutions
- Send a new user message with prompt() (or queue one via followUp() and then continue()) instead of bare continue().
- Before continuing, check agent.state.messages.at(-1)?.role and skip or prompt when it is 'assistant'.
- Remember steer()/followUp() messages are drained automatically by continue() - queue before calling if you want them used.
Example fix
// before
await agent.prompt("hi");
await agent.continue(); // last message assistant, queues empty -> throws
// after
await agent.prompt("hi");
await agent.followUp({ role: "user", content: [{ type: "text", text: "keep going" }], timestamp: Date.now() });
await agent.continue(); // drains the queued follow-up Defensive patterns
Strategy: validation
Validate before calling
const last = agent.state.messages.at(-1);
if (last && last.role === "assistant" && !agent.hasQueuedMessages()) {
await agent.prompt("continue"); // new input required
} else {
await agent.continue(); // queues drain automatically
} Type guard
function canContinue(agent: Agent): boolean {
const last = agent.state.messages.at(-1);
return last !== undefined && (last.role !== "assistant" || agent.hasQueuedMessages());
} Try / catch
try {
await agent.continue();
} catch (err) {
if (err instanceof Error && err.message.includes("role: assistant")) {
await agent.prompt("keep going"); // fallback: send new input
} else {
throw err;
}
} Prevention
- Check the transcript tail role before calling continue().
- Queue follow-ups before continue() if you want them drained on this call.
- Do not loop continue() after every run without new input.
When it happens
Trigger: Calling continue() right after a completed run whose final message is the assistant reply, with nothing queued via steer()/followUp(); resume/regenerate buttons mapped to continue() after a normal finish; continue loops that run after every turn without checking the last role.
Common situations: Chat UI continue buttons; orchestrators iterating continue() after each run; tests that continue a finished conversation.
Related errors
- Agent is already processing a prompt. Use steer() or followU
- Agent is already processing. Wait for completion before cont
- No messages to continue from
- Cannot continue from message role: assistant
- Agent is already processing. Wait for completion before rese
AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24).
Data as JSON: /api/errors/a262bd2ce29e460e.
Report an issue: GitHub.