Skip to content

fix(opencode): compare ID timestamps numerically in isAfter() to handle 48-bit overflow - #42684

Closed
ar1vit0r wants to merge 1 commit into
anomalyco:devfrom
ar1vit0r:fix/id-timestamp-comparison
Closed

fix(opencode): compare ID timestamps numerically in isAfter() to handle 48-bit overflow#42684
ar1vit0r wants to merge 1 commit into
anomalyco:devfrom
ar1vit0r:fix/id-timestamp-comparison

Conversation

@ar1vit0r

@ar1vit0r ar1vit0r commented Aug 15, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #42639

Type of change

  • Bug fix

What does this PR do?

MessageV2.latest() uses isAfter() to find the most recent user/assistant message. The tiebreaker compared IDs as strings, but the 6-byte timestamp field in ascending IDs overflows 2^48 since ~2023, making string comparison give wrong results after wraparound. Now extracts the timestamp hex from each ID and compares numerically via BigInt.

How did you verify your code works?

Code review -- the overflow math is straightforward. The primary comparison (time.created) is unchanged; only the equal-timestamp tiebreaker is fixed.

Checklist

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

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

Copy link
Copy Markdown
Contributor

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

@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

Scope: replaces the string tiebreak in isAfter() (info.id > other.id) with a numeric comparison of the 12-hex-char timestamp embedded in message IDs, claiming string comparison breaks after 48-bit wraparound.

  • The premise doesn't hold up, and the change doesn't fix wraparound. Per packages/opencode/src/id/id.ts, IDs are msg_ + exactly 12 zero-padded hex chars (timeBytes.toString("hex")) + random suffix. For fixed-width hex, lexicographic and numeric comparison are identical — so this is behaviorally a no-op for generator-produced IDs. More importantly, the wraparound itself (the 48-bit field packs ms*0x1000+counter, overflowing every ~2.18 years) corrupts both orderings equally: a post-wrap ID's hex is smaller than a pre-wrap one whether you compare it as string or BigInt. A real fix needs unwrap-aware logic (e.g., modular-window comparison) or wall-clock time.created authority — this PR changes neither.
  • New crash risk: BigInt("0x" + hex) throws on non-hex characters. MessageID is only validated as startsWith("msg"), so any hand-crafted/imported ID (tests in this very repo use e.g. msg_message_diff_test) makes latest() throw a SyntaxError. The old string compare was total and never threw. Please fall back to string comparison on parse failure at minimum.
  • Lost determinism: the previous full-ID comparison used the random suffix as a stable tiebreak for equal timestamps; returning 0 for equal hex now makes isAfter depend on input order in that case.
  • Nits: comment reads "The6-byte"; indexOf("_") picks the first underscore, which is fine for msg_ but silently mis-slices hypothetical multi-underscore prefixes.
  • Recommend reverting or reworking: as written it adds a crash path and removes a tiebreak without fixing the reported symptom.

@ar1vit0r

Copy link
Copy Markdown
Author

Closing — the change is a no-op (fixed-width hex string comparison is already identical to numeric comparison of the embedded timestamp) and adds a crash path on non-hex IDs. The existing tiebreak is correct. Thanks to @Enough1122 for the thorough review.

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.

Message IDs wrap around, silently dropping new user input in sessions with history

2 participants