Skip to content

fix: ensure assistant message ID is always greater than parent user message ID#22988

Open
lightrabbit wants to merge 2 commits intoanomalyco:devfrom
lightrabbit:fix/message-ordering-v2
Open

fix: ensure assistant message ID is always greater than parent user message ID#22988
lightrabbit wants to merge 2 commits intoanomalyco:devfrom
lightrabbit:fix/message-ordering-v2

Conversation

@lightrabbit
Copy link
Copy Markdown

Issue for this PR

Closes #15657

Type of change

  • Bug fix

What does this PR do?

Fixes a bug where the first assistant message after a user message could appear above the user message in the UI timeline, making it invisible to the user.

Root cause: When the frontend generates a user message ID using Identifier.ascending("message") and sends it to the backend, the backend creates the assistant message using MessageID.ascending(). Both calls use Date.now() internally. If the frontend and backend clocks are slightly out of sync (even by a few hundred milliseconds), the assistant message ID can end up lexicographically smaller than the user message ID. Since the UI sorts messages by ID string order, the assistant message appears before the user message.

Fix: Introduces Identifier.ascendingAfter(prefix, afterID) which operates in the truncated 48-bit encoding space to guarantee the generated ID is strictly greater than afterID. All four sites that create assistant messages now use MessageID.ascendingAfter(parentID) instead of MessageID.ascending().

Key detail: the ID encoding only stores the lower 48 bits of timestamp * 0x1000 + counter, so the comparison must happen in that truncated space rather than against raw Date.now() values.

How did you verify your code works?

  • Added 6 unit tests in test/id/identifier.test.ts covering:
    • Normal case (backend time after frontend)
    • Same-millisecond parent ID
    • 300ms clock skew (frontend ahead)
    • 5s clock skew (extreme case)
    • 100 unique IDs generated from same parent
    • Non-interference with ascending() monotonicity
  • All tests pass
  • Typecheck passes with 0 new errors in changed files

Screenshots / recordings

N/A (backend logic change)

Checklist

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

@lightrabbit lightrabbit force-pushed the fix/message-ordering-v2 branch from 55edf07 to 8368d5d Compare April 17, 2026 02:04
lightrabbit and others added 2 commits April 17, 2026 10:12
When frontend and backend clocks are out of sync, the assistant message ID\ngenerated by the backend could end up smaller than the parent user message\nID, causing the assistant message to appear above the user message in the\nUI timeline.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Replace MessageID.ascending() with MessageID.ascendingAfter(parentID) at\nevery site where an assistant message is created, ensuring the assistant\nmessage ID is always strictly greater than its parent user message ID.\n\nAdds 6 unit tests covering normal case, same-millisecond, clock skew\n(300ms and 5s), uniqueness, and non-interference with ascending().

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@lightrabbit lightrabbit force-pushed the fix/message-ordering-v2 branch from 8368d5d to 2e297fd Compare April 17, 2026 02:12
@lightrabbit
Copy link
Copy Markdown
Author

Note: this PR addresses the same root issue as #21572 (Closes #15657) but takes a complementary approach.

#21572 fixes this at the UI/rendering layer — it introduces a sortMessages() utility that sorts messages by time.created instead of relying on raw ID ordering. This prevents misordered display regardless of how the IDs were generated.

This PR fixes this at the ID generation layer — it ensures that assistant message IDs are always lexicographically greater than their parent user message IDs by introducing Identifier.ascendingAfter(). This prevents the root cause (clock skew producing out-of-order IDs) rather than patching the symptom in the UI.

The two approaches are not mutually exclusive. Fixing the ID generation prevents the ordering violation from ever reaching the UI, while the UI-level sort provides a defense-in-depth against any other sources of misordered messages. Together they provide a more robust solution than either alone.

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: Message ordering broken when client clock is out of sync with server

1 participant