Skip to content

fix(session): end the turn loop by reply parent, not message ID order - #38387

Draft
Itsnotaka wants to merge 4 commits into
anomalyco:devfrom
Itsnotaka:fix/session-loop-client-message-id
Draft

fix(session): end the turn loop by reply parent, not message ID order#38387
Itsnotaka wants to merge 4 commits into
anomalyco:devfrom
Itsnotaka:fix/session-loop-client-message-id

Conversation

@Itsnotaka

@Itsnotaka Itsnotaka commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #35741

Type of change

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

What does this PR do?

Fixes a session turn loop caused by treating message IDs as timestamps.

Clients may supply messageID, and the public schema only requires the msg prefix. If that ID sorts after IDs created by the server, MessageV2.latest can keep selecting an older user message. A completed assistant reply then fails the loop's ID-order exit check, so the model is prompted again.

This change:

  • selects the latest user, assistant, finished assistant, and pending task boundary by time.created, with ID as the same-millisecond tie-breaker
  • ends a completed turn when the assistant's parentID matches the latest user message
  • keeps existing client-supplied ID formats working and recovers sessions that already contain a non-monotonic ID
  • adds unit coverage for message and task ordering plus a two-turn regression using the UUID observed in the affected session

How did you verify your code works?

  • bun test test/session --timeout 30000 --only-failures: 370 passed, 7 skipped, 1 todo
  • bun test test/session/message-v2.test.ts test/session/prompt.test.ts --timeout 30000 --only-failures: 94 passed, 1 skipped
  • bun typecheck from packages/opencode
  • repository pre-push bun turbo typecheck: 30 successful targets
  • Prettier check and git diff --check

Screenshots / recordings

Not applicable. This changes session ordering and loop behavior without changing UI.

Checklist

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

@github-actions

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 the needs:compliance This means the issue will auto-close after 2 hours. label Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

I found a potentially related PR:

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

This PR appears to address a very similar issue — preventing an agent loop caused by non-monotonic message IDs, which directly relates to PR #38387's core problem where client-supplied UUIDs break message ID ordering and cause an infinite turn loop. Both PRs are tackling the session loop exit condition issue stemming from message ID ordering problems.

The loop exited only when `lastUser.id < lastAssistant.id`, and
`MessageV2.latest` picks the newest user/assistant by lexicographic ID.
Message IDs from `Identifier.ascending` sort by creation time, so that
holds for IDs this loop mints.

`session.promptAsync` lets a client supply the user message ID, and it is
validated only by `Schema.isStartsWith("msg")`. A client ID that does not
sort below the IDs minted afterwards is therefore permanently the maximum:
it is always `lastUser`, the exit test is never true, and the loop keeps
re-prompting the model with a finished conversation until the process is
restarted.

Compare `lastAssistant.parentID === lastUser.id` instead. Every assistant
message in this file is created with `parentID` set to the user message it
answers, so the check reads the same intent from data the loop owns and is
unaffected by the format of a client-supplied ID.
`session.promptAsync` accepts a caller-supplied user message ID and stores
it as-is; `MessageID` validates only the `msg` prefix. Ordering-sensitive
code such as `MessageV2.latest` then compares it against IDs minted by
`Identifier.ascending`, so an ID in any other format sorts arbitrarily
against the rest of the session.

Add `Identifier.isAscending` and check a supplied ID at both prompt entry
points, so a client that mints its own IDs gets a clear error at the call
that introduces the problem instead of a session that misbehaves later.
@Itsnotaka
Itsnotaka force-pushed the fix/session-loop-client-message-id branch from 9854b66 to be070f6 Compare July 22, 2026 21:07
@github-actions github-actions Bot removed needs:compliance This means the issue will auto-close after 2 hours. needs:issue labels Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

1 participant