Skip to content

fix(session): use transcript position instead of lexical ID compare in prompt loop#24379

Open
tiffanychum wants to merge 1 commit intoanomalyco:devfrom
tiffanychum:fix/23490-prompt-id-ordering
Open

fix(session): use transcript position instead of lexical ID compare in prompt loop#24379
tiffanychum wants to merge 1 commit intoanomalyco:devfrom
tiffanychum:fix/23490-prompt-id-ordering

Conversation

@tiffanychum
Copy link
Copy Markdown

Issue for this PR

Closes #23490

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

SessionPrompt.run decided transcript ordering by comparing opaque message IDs:

lastUser.id < lastAssistant.id        // loop-exit check
m.info.id <= lastFinished.id          // post-finish reminder check

The HTTP API explicitly accepts caller-supplied messageID on prompt input, but those checks assume IDs always preserve OpenCode's internal monotonic ordering. When a caller supplies a messageID that lexically sorts greater than MessageID.ascending() output (e.g. msg_zzzzzzzz...), the comparison reverses and:

  1. Loop-exit at finish="stop" no longer fires → loop runs an extra iteration after the assistant turn already completed.
  2. The next request OpenCode sends ends with an assistant message → providers like Anthropic via OpenRouter reject it with "last message must be a user message".

The fix tracks lastUser/lastAssistant/lastFinished array indices during the same backward walk that already collects those messages, then uses array position (the canonical transcript order produced by MessageV2.filterCompactedEffectpage()ORDER BY time_created, id) for both decisions. No new traversals; behavior is unchanged for OpenCode-generated monotonic IDs.

Two sites updated:

  • Loop-exit check at the top of runLoop.
  • Post-finish system-reminder loop after the user has sent additional messages.

How did you verify your code works?

Added a regression test in packages/opencode/test/session/prompt.test.ts:

it.live("loop exits when caller-supplied user messageID lex-sorts after assistant id", ...)

It seeds a session with:

  • A user message whose ID is msg_zzzzzzzzzzzzzzzzzzzzzzzz (lex-greater than any hex-prefixed MessageID.ascending()),
  • An auto-ID assistant message with finish: "stop" and time.created strictly greater so transcript ordering is unambiguous.

Then asserts prompt.loop returns the assistant message and the LLM mock receives 0 calls.

Verification:

  • New test fails on dev (loop times out re-calling LLM with no replies queued — exactly the reporter's symptom).
  • New test passes with the fix applied.
  • Full session test suite green: bun test test/session/ → 315 pass / 0 fail.
  • bun typecheck clean in packages/opencode.
  • bun turbo typecheck clean repo-wide (13/13).

Screenshots / recordings

N/A — non-UI fix.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

…n prompt loop

SessionPrompt.run compared lastUser.id < lastAssistant.id (and
m.info.id <= lastFinished.id) to decide transcript ordering. The HTTP
API explicitly accepts caller-supplied messageIDs, so opaque ID order
is not guaranteed to match transcript order. A custom user ID that
lex-sorts after the assistant's auto-generated ID caused the loop to
keep running past finish=stop, emitting an assistant-last transcript
that downstream providers reject.

Track lastUser/lastAssistant/lastFinished indices in the same backward
walk and use array positions (canonical transcript order from
filterCompactedEffect) for the loop-exit and post-finish reminder
checks. Behavior is unchanged for OpenCode-generated monotonic IDs.

Closes anomalyco#23490
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SessionPrompt.run compares message IDs instead of transcript order/time, which breaks custom messageIDs accepted by the API

1 participant