Skip to content

Run loop continues on orphaned interrupted tools, triggering "model does not support assistant message prefill" 400 errors #26177

@edevil

Description

@edevil

Bug

When a stream attempt is aborted or retried mid-tool, opencode's
processor.cleanup() force-marks the orphaned tool_use as
state.status: "error" with metadata.interrupted: true. The next
iteration of the run loop's tool-call check counts this orphan as open
work and fires another LLM request. convertToModelMessages then splits
the assistant message around the orphan and the resulting history ends
with an assistant turn — which Anthropic rejects with HTTP 400:

This model does not support assistant message prefill.
The conversation must end with a user message.

The session is then stuck: every retry replays the same corrupted
history from storage and re-triggers the 400.

Trigger sequence

  1. Model emits a tool_use block.
  2. Stream is aborted/retried before the tool completes (network blip,
    user cancel, EmptyOther truncation, etc.).
  3. processor.cleanup() writes
    { status: "error", metadata: { interrupted: true } } on the tool
    part to mark it as not-running.
  4. The run loop's hasToolCalls check at the bottom of prompt.ts
    sees the error tool, treats it as open work, and continues the loop.
  5. Next LLM request is built from the assistant message that contains
    the orphan; the AI SDK splits the message and the request history
    ends with an assistant turn.
  6. Anthropic returns 400 (or, for non-prefill-supporting models in
    general, an equivalent error).

Real-world evidence

In my own opencode session database I found:

  • 71 orphaned-interrupted tool parts matching the shape
    { state.status: "error", metadata: { interrupted: true }, error: "Tool execution aborted" }.
  • 15 explicit prefill 400 errors across 6 sessions,
    predominantly on claude-opus-4-6 (14 occurrences) and
    claude-opus-4-7 (1).

In one session the temporal correlation is direct:

  • orphan tool part created at time.start: 1777464448632
  • prefill 400 error logged at time_created: 1777464448637
  • 5 ms apart — the next LLM request was the failure

Suggested fix

Refactor the inline tool-call check in the run loop into a helper that
excludes orphaned-interrupted tool parts in addition to provider-executed
ones. They don't represent work the model is waiting on — the model
never saw the result and the next user turn supersedes them.

Environment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions