Skip to content

fix: prevent agent loop self-reply caused by non-monotonic message IDs - #35872

Open
lileilei-camera wants to merge 1 commit into
anomalyco:devfrom
lileilei-camera:fix/webchat-self-reply-loop
Open

fix: prevent agent loop self-reply caused by non-monotonic message IDs#35872
lileilei-camera wants to merge 1 commit into
anomalyco:devfrom
lileilei-camera:fix/webchat-self-reply-loop

Conversation

@lileilei-camera

@lileilei-camera lileilei-camera commented Jul 8, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #35741

Type of change

  • Bug fix

What does this PR do?

Fix the WebChat agent self-reply loop where the LLM hallucinates its own output as user input, producing 12+ rounds of self-reply in a single session.

Root cause: MessageID.ascending() generates IDs using a counter-based scheme where lexicographic string comparison can invert chronological order at ~2.8% probability under millisecond-dense creation. The loop exit condition in SessionPrompt.run and message selection in message-v2.ts both rely on lastUser.id < lastAssistant.id, which fails when IDs are non-monotonic.

Fix: Replace all message ID string comparisons with timestamp-based comparison (time.created):

  • prompt.ts line 1115: lastUser.time.created < lastAssistant.time.created
  • message-v2.ts lines 591-593: 3 ID comparisons in latest() → timestamp comparisons

This approach was inspired by the root cause analysis in #28986.

How did you verify your code works?

  1. Rebuilt binary with WebChat frontend embedded
  2. Smoke test: binary launches and responds normally
  3. Reproduced the self-reply loop before the fix (see [Bug] WebChat: LLM hallucinates user response — answers own question without user input #35741 for 12-round hallucination log)
  4. After the fix, no self-reply loops observed in new sessions

Screenshots / recordings

Not applicable - behavioral fix, no UI changes.

Checklist

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

Replace lexicographic string comparison of message IDs with
timestamp-based comparison in the agent loop exit condition and
the `MessageV2.latest()` function.

`MessageID.ascending()` can produce IDs whose lexicographic
order is inverted relative to creation time when two messages
are created within the same millisecond (~2.8% probability).
The string comparison `lastUser.id < lastAssistant.id` can
evaluate to `false` even though the assistant message was
created after the user message, causing the loop to continue
processing the LLM's own output as if it were a new user
message — producing hallucinated self-replies and phantom
conversations.

This is most visible in WebChat where the LLM asks clarifying
questions and then immediately hallucinates a user response
without any actual input.

Root cause analysis: anomalyco#28986
Fixes: anomalyco#35741

Changes:
- prompt.ts: exit condition uses time.created instead of id
- message-v2.ts: latest() uses time.created instead of id
  (3 comparison sites)
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. and removed needs:issue needs:compliance This means the issue will auto-close after 2 hours. labels Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@Ejmathewp

Copy link
Copy Markdown

This would be wonderful to get merged! I'm having to run a local build with this fix because GLM-5.2 is utterly unusable with this bug!

@sspotanin

Copy link
Copy Markdown

Confirming this reproduces on v1.18.5 (macOS, opencode serve + web UI).

Concrete example from a real session — one user message produced two
terminal assistant siblings:

message ID prefix time_created input cache.read
user msg_faf2259**52**001… +0 ms
assistant #1 msg_faf2259**4f**001… +103 ms 63 780 13 312
assistant #2 msg_faf231fc4… +51 s 2 647 76 800

The assistant ID sorts lexicographically before the user ID (4f < 52)
despite being created 103 ms later, so lastUser.id < lastAssistant.id
evaluates false and the loop re-enters. The second call has the classic
tiny-input / huge-cache-read signature.

Downstream effect: the model sees its own first answer in context and
hallucinates it as user input, producing confused follow-ups.

This matches the 2.8% collision rate documented in #28986. The fix is
correct and minimal. Would appreciate a maintainer review — this has been
open across 5+ duplicate issues and 4 PRs since February without a merge.

Maintainers please react to it!

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.

[Bug] WebChat: LLM hallucinates user response — answers own question without user input

3 participants