fix(extensions): keep SQLite usage capture off the event loop - #4981
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 504a400486
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
seratch
left a comment
There was a problem hiding this comment.
Thanks for the fix. Moving usage capture off the event loop addresses a real blocking path, but the new await still allows usage to move between turns on the same branch.
While capture is pending, another coroutine can append a turn or pop and replace the original turn. Neither operation changes the clear generation. Capture then selects the newer turn and its anchor, so the write guard accepts the older result’s usage against that turn.
Please preserve the original turn’s attribution across the capture wait without restoring blocking database access. Add controlled regressions for append and pop/replacement during that wait, asserting that the newer turn’s usage remains unchanged. The existing removed/reused-turn tests pause after capture and miss this window.
|
Thanks for catching this. You're right: the earlier tests paused after capture and missed the window before it selected a turn. Fixed in 5a7b58d. Capture now reserves its place before the first await, alongside appends and pops on the same session. Later history changes wait until that capture completes; the existing anchor check still prevents the eventual write from reaching a removed or replacement turn. The turn lookup and usage write both run in workers. The new append and pop/replacement regressions both reproduced the issue before the fix: the older run overwrote the newer turn's 11 tokens with 80. They now pass and assert that the newer turn's usage stays unchanged. I also checked queued failure/cancellation and session reuse across event loops. All 140 session tests and the full verification stack passed (9,653 tests passed, 78 skipped). |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a7b58d31b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
markstuart-oai
left a comment
There was a problem hiding this comment.
Reviewed e78c8e28bd4bccbe89795319a8d78f08607e00f3 against the current-main merge base ae5803f3ccd59899c14e0ee2f356614933880a68 (the effective two-file change). The off-loop capture and cancellation settlement look sound, but one branch lifecycle race remains: a later queued append can recreate a branch after deletion succeeds.
Source-only review, including the controlled concurrency tests; no local repository workloads were run. Hosted checks on this head: 18 passed, 3 still running.
This pull request fixes an event-loop stall in
AdvancedSQLiteSession.store_run_usage()while preserving branch and turn ownership when session operations overlap.Summary
Move SQLite turn capture into a worker and keep capture plus the guarded usage write within the existing cancellation handling, so caller cancellation waits for storage to settle. Order capture with appends, pops, and branch deletion on the same session instance: later history changes cannot make an earlier run select a newer or replacement turn. Release that ordering barrier after capture; the existing row-anchor check protects the later write.
Keep each queued append on the branch selected when its call begins, even if a branch switch completes while it waits. Publish branch and clear generation together and capture them as one value, preventing a clear or refresh from combining an old branch with a new generation. Preserve the existing clear/reset behavior and initialize the stored generation when reopening a session.
Order branch deletion behind earlier queued appends so an append cannot recreate a successfully deleted branch. Controlled regressions cover both forced deletion and deletion after a branch switch.
Public APIs and the database schema are unchanged. Independent instances and processes retain the existing database-lock ordering; this change adds no cross-process call-entry ordering or SQLite read transaction.
Test plan
7927b85b, including native macOS sandbox coverage, Linux Python 3.10–3.14, and Windows Python 3.10/3.13.Issue number
No existing issue.
Checks
.agents/skills/code-change-verification/scripts/run.sh