can1357/oh-my-pi · error

No messages to continue from

Error message

No messages to continue from

What it means

Agent.continue() throws this when the transcript is empty (messages.length === 0) AND there are no queued steering or follow-up messages to deliver as the opening turn (packages/agent/src/agent.ts:1250). continue() resumes an existing conversation; with zero messages and an empty queue there is nothing to resume. The queued-message checks exist specifically so idle-drain callers don't spin on an undeliverable queue (issue #6344).

Source

Thrown at packages/agent/src/agent.ts:1250

			const messages = this.#state.messages;
			if (messages.length === 0) {
				// An empty transcript has nothing to resume, but a queued steer/follow-up
				// must still be delivered as the opening turn — mirroring the assistant-tail
				// branch below. Throwing here leaves the message undeliverable, and idle-drain
				// callers (AgentSession#scheduleQueuedMessageDrain) re-arm continue() on every
				// microtask because hasQueuedMessages() never clears, spinning an unbounded
				// allocation loop until OOM (issue #6344).
				const queuedSteering = await this.#dequeueSteeringMessagesAfterHooks(dequeueSignal);
				if (queuedSteering.length > 0) {
					await this.#runLoop(queuedSteering, { skipInitialSteeringPoll: true }, signal, true);
					return;
				}
				const queuedFollowUp = await this.#dequeueFollowUpMessagesAfterHooks(dequeueSignal);
				if (queuedFollowUp.length > 0) {
					await this.#runLoop(queuedFollowUp, undefined, signal, true);
					return;
				}
				throw new Error("No messages to continue from");
			}
			if (messages[messages.length - 1].role === "assistant") {
				// A tail with unpaired runnable tool calls resumes by re-executing
				// them (see `unpairedToolCallTail` in agent-loop). This must win over
				// queued-message delivery: injecting a message between the tool_use
				// blocks and their results would break the provider's pairing
				// invariant. Queued messages drain inside the resumed loop instead.
				if (unpairedToolCallTail(messages)) {
					await this.#runLoop(undefined, undefined, signal, true);
					return;
				}
				const queuedSteering = await this.#dequeueSteeringMessagesAfterHooks(dequeueSignal);
				if (queuedSteering.length > 0) {
					await this.#runLoop(queuedSteering, { skipInitialSteeringPoll: true }, signal, true);
					return;
				}

				const queuedFollowUp = await this.#dequeueFollowUpMessagesAfterHooks(dequeueSignal);

View on GitHub (pinned to 9690622007)

Solutions

  1. Check that the transcript is non-empty before calling continue(): if messages.length === 0 and nothing is queued, call prompt() with a new user message instead.
  2. If you intended to start a conversation, use prompt() rather than continue().
  3. If resuming from persisted state, verify the session file actually loaded messages (guard against a missing/empty session).
  4. If you meant to inject work, queue it via steer()/followUp() first so continue() can deliver it.

Example fix

// before
await agent.continue(); // throws when transcript is empty

// after
if (hasQueuedMessages(agent) || lastMessageRole(agent) !== undefined) {
  await agent.continue();
} else {
  await agent.prompt("resume work on task X");
}
Defensive patterns

Strategy: validation

Validate before calling

function canContinue(agent: Agent): boolean {
  const msgs = getMessages(agent);
  return msgs.length > 0 || hasQueuedMessages(agent);
}
if (canContinue(agent)) {
  await agent.continue();
} else {
  await agent.prompt(initialUserMessage);
}

Try / catch

try {
  await agent.continue();
} catch (err) {
  if (err instanceof Error && err.message === "No messages to continue from") {
    await agent.prompt("Starting new task: ...");
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling agent.continue() on a freshly constructed or reset() Agent (no messages) with no pending steering/follow-up entries; calling continue() after clearMessages() on an empty session.

Common situations: Resume/retry logic that calls continue() unconditionally at startup before any prompt was ever made; a crash-recovery path that finds an empty transcript file; calling continue() after reset() drained both history and queues.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/ffd7caca6534fa29. Report an issue: GitHub.