Repository navigation
fix(session): delete reverted messages boundary-last and tie-break ids by raw order - #42819
NamedIdentity wants to merge 2 commits into
Conversation
Revert cleanup removed the boundary first. Each removal is a separate durable transaction, so an interruption after the boundary left the remaining reverted rows unreachable: the next cleanup's findIndex returned -1, remove was empty, and clearRevert still discarded the only marker that could have located them. Those rows then rejoined the transcript as ordinary history and were sent to the model. Iterating newest-first makes the boundary its own progress marker, so an interrupted cleanup leaves a state the next one completes. The part loop had the same shape and the same fix.
…ation Message ordering tie-broke equal time.created with localeCompare, while storage pages with ORDER BY time_created, id under SQLite's BINARY collation. The two disagree: for ids differing only in suffix case, en-US collation and raw byte order sort them oppositely, so a paginated fetch and the client could order the same pair differently. 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. Applies to the TUI message store and the shared transcript. The session list sort is left alone -- it has no time component and feeds a binary search whose relation would have to change with it.
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
fix(session): delete reverted messages boundary-last and tie-break ids by raw order|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
Review of the revert-deletion ordering fix — the boundary-last strategy and its rationale comment are well reasoned (interrupted cleanups stay resumable because the surviving marker still locates the remaining rows):
|
|
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
Issue for this PR
Closes #42816
Type of change
What does this PR do?
Two fixes to the ID rollover ordering work.
1. Revert cleanup deletes the boundary message first.
SessionRevert.cleanupremoves messages from the revert boundary onward. The boundary isremove[0]when nopartIDis set, and eachremoveMessagecommits separately. If that loop is interrupted, the boundary is already gone but the rest are not. The next cleanup callsfindIndexfor the boundary, gets-1, soremoveis empty and it deletes nothing.clearRevert()still runs at the end, discarding the marker that could have located those rows. They stay in the session permanently and get sent to the model on the next turn.Using
.toReversed()deletes the boundary last, so it works as a progress marker. An interrupted cleanup then leaves a state the next one can finish. The part loop below it has the same problem and the same fix.2. The
localeComparetie-break does not match storage order.Storage pages with
ORDER BY time_created, id, and SQLite's default collation is BINARY.sync.tsxandShare.tsxtie-break the same data withlocaleCompare, which is locale collation. For two messages sharing atime.createdthese can return opposite results, so the client and the database can disagree about which comes first. Ids minted in the same millisecond by different processes share the timestamp prefix, so the random mixed case suffix decides, and that is exactly where the two collations differ.localeComparecan also return 0 for two distinct strings that are canonically equivalent.MessageIDonly requires amsgprefix, so two different primary keys can compare equal and the sort becomes dependent on input order.Raw
<and>match what storage already does.How did you verify your code works?
Read the affected paths and traced the interruption sequence above. Confirmed
toReversed()is already used elsewhere in the repo, so there are no target or lib concerns.Both fixes have been running in our fork for a day. We hit the same rollover in production on 2026-08-14, wrote the same core fix independently, then found yours had landed. These two turned up while auditing ours.
I have not run the full test suite against this branch.
Screenshots / recordings
Not a UI change.
Checklist