anomalyco / anomalyco/opencode

[BUG] Two ordering-correctness gaps remain after the ID-rollover fix

Open
#42,816 3 comments 0 reactions 1 assignee View on GitHub

@kitlangton is already working on this.

Since Aug 15, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
Avg merge
7h 2m
Merged PRs (30d)
384

Description

Description

Following the identifier rollover work that landed on dev: isAfter() in MessageV2.latest(), the parentID completion gate, mtime truncation retention, positional revert cleanup.

We run a fork and hit the same wrap in production on 2026-08-14. Identifier.create() keeps only the low 36 bits of Date.now(), so ascending ids wrap every 2^36 ms, about 795 days, and 7,228 of our 7,235 sessions stopped producing assistant messages. We implemented a fix independently and arrived at the same core design. Auditing ours turned up two gaps still present here, plus two smaller observations.

1. An interrupted revert cleanup can resurrect reverted messages.

SessionRevert.cleanup deletes from the revert boundary onward, boundary first, each removal its own durable transaction. If the loop is interrupted partway the boundary is already gone. The next cleanup calls findIndex for a message that no longer exists, gets -1, deletes nothing, and clearRevert() still runs and discards the only marker that could have located the rest.

Those messages are now permanent transcript and go to the model on the next turn. From the user's side, content they explicitly reverted comes back with no error.

Deleting newest-first makes the boundary a progress marker, so an interrupted cleanup leaves a state the next one completes:

- for (const msg of remove) {
+ for (const msg of remove.toReversed()) {

The part loop below it has the same shape and the same fix.

2. Client and storage can disagree on message order.

Storage pages with ORDER BY time_created, id, which is SQLite BINARY collation. packages/tui/src/context/sync.tsx:55 and packages/web/src/components/Share.tsx:79 tie-break the same data with localeCompare, which is locale collation. For two messages created in the same millisecond these can order oppositely. Ids minted in the same millisecond by different processes share the packed prefix, so the mixed case random suffix decides, precisely where the collations diverge.

localeCompare also returns 0 for canonically equivalent distinct strings. MessageID only requires a msg prefix and is not restricted to ASCII, so two distinct primary keys can compare equal and the sort becomes input order dependent.

- a.time.created - b.time.created || a.id.localeCompare(b.id)
+ a.time.created - b.time.created || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)

Two smaller things, not in the PR.

packages/app still orders by raw id. The server and TUI were converted, the App store was not: context/global-sync/bootstrap.ts:192, context/server-sync.tsx:429, pages/layout/helpers.ts:15, pages/session.tsx:841. Sessions there will order incorrectly across a wrap. We fixed our copy, but it touches about 24 sites and wants a shared comparator helper, so it seemed better raised than dropped on you unrequested.

The completion gate has no chronological fallback. parentID === lastUser.id is immune to both wrap and clock movement, which is why we adopted it too. But where the latest assistant's parentID points at an earlier user than the chronologically latest one, the equality never holds. We found 31 such sessions in a 251k message database, each with a chronologically later synthetic user carrying a legacy short format id while the latest assistant is parented to the ordinary user before it. Reading your gate against that shape it would not exit. Ours falls back to a chronological comparison. Flagging in case those histories exist elsewhere; if parentID only is deliberate, ignore this.

Next wrap is 2028-10-17. Widening the identifier is worth doing before then but cannot repair existing data, since the 48 bit space is exhausted and no encoding reorders ids already stored. That is why fixing the call sites was the right call.

OpenCode version

Fork of v1.17.11; defects verified against dev @ 4643e65ad6 (v1.18.18).

Operating System

Windows 11

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.