Repository navigation
fix(server): replaying a command no longer freezes the server - #17041
Conversation
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused performance fix that pins an already-existing command ID index for replay queries, leaving other read paths unchanged. The accompanying test verifies both replay results and the selected query plan, with no schema, default, deployment, or static-analysis changes. You can add or adjust custom eligibility rules. Learn more. |
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (2)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughCommand-filtered event reads now use the command-ID index. A persistence test checks that replay returns both matching events and that the traced query uses that index. ChangesCommand-Filtered Event Reads
Priority: ⬆️ High Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to No actionable merge-blocking issue is identified for command replay; the change is ready for normal merge checks. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Re-dispatching a command that already has a receipt (for example an agent retrying delegate_task with the same clientRequestId) reads that command's events back. The query's sequence range made SQLite walk every V2 event instead of using the command_id index. On an 8 GB database that took 24-29 s of synchronous SQLite, so every client lost its socket in the meantime. Pin idx_orch_events_command_id when filtering by command. Measured on a real database through readAgentEvents: 29,014 ms before, 23 ms after. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
11b6fbe to
074c357
Compare
## What's Changed * chore(review): remove the custom Approvability check by @esthor in pingdotgg/t3code#17018 * perf(mobile): show the cached thread list sooner and stop the freeze after it by @juliusmarminge in pingdotgg/t3code#16713 * fix(server): replaying a command no longer freezes the server by @SunkenInTime in pingdotgg/t3code#17041 * fix(server): opening a long thread no longer blocks the server for a second by @SunkenInTime in pingdotgg/t3code#17029 * fix(server,web,mobile): thread links reference the thread id, not a baked title by @juliusmarminge in pingdotgg/t3code#17017 * fix(web): messages with quotes or links no longer collapse when short by @flamboh in pingdotgg/t3code#16624 * refactor(web): PR loading skeleton shares the detail panel's layout by @flamboh in pingdotgg/t3code#15583 * fix(web): PR file headers keep the file name in narrow panels by @flamboh in pingdotgg/t3code#15562 * fix(web): PR timeline no longer shifts when its scrollbar appears by @flamboh in pingdotgg/t3code#15588 * fix(server): held queued wakes no longer keep delegated tasks running by @juliusmarminge in pingdotgg/t3code#17028 * fix(server): a message sent during a rollback no longer undoes it by @t3dotgg in pingdotgg/t3code#17079 * fix(web): PR file stats ignore the hide-whitespace toggle by @flamboh in pingdotgg/t3code#16162 * perf(mobile): omit duplicated turn items from bounded thread snapshots by @juliusmarminge in pingdotgg/t3code#15385 * perf(mobile): pause elapsed-time timers on hidden thread screens by @juliusmarminge in pingdotgg/t3code#15397 * fix(mobile): pause hidden home thread list updates by @juliusmarminge in pingdotgg/t3code#15705 * fix(mobile): restore file viewer insets and glass header by @juliusmarminge in pingdotgg/t3code#17073 * fix(web): PR code toolbar no longer overlaps in narrow panels by @flamboh in pingdotgg/t3code#15561 * fix(web): PR commit menu no longer stretches across the window by @flamboh in pingdotgg/t3code#15560 * fix(web): command palette scrollbar no longer clipped at the top by @flamboh in pingdotgg/t3code#17035 * fix(web): Usage breadcrumb stays centered on small viewports by @flamboh in pingdotgg/t3code#15552 * fix(mobile): stop refreshing Git status on streamed thread updates by @juliusmarminge in pingdotgg/t3code#15893 * fix(mobile): skip move indexes for empty and single-thread sections by @juliusmarminge in pingdotgg/t3code#16115 * perf(mobile): reuse encoded rows in shell cache saves by @juliusmarminge in pingdotgg/t3code#16129 * perf(mobile): skip showcase subscriptions in normal builds by @juliusmarminge in pingdotgg/t3code#16131 * perf(mobile): remove unused Home project sorting by @juliusmarminge in pingdotgg/t3code#16177 * perf(mobile): reduce move-menu index allocations by @juliusmarminge in pingdotgg/t3code#16256 * perf(mobile): skip impossible thread-key lookups by @juliusmarminge in pingdotgg/t3code#16263 * fix(mobile): collect UI runtime garbage on iOS memory warnings by @juliusmarminge in pingdotgg/t3code#16296 * fix(mobile): stop Git sheet refresh loop by @juliusmarminge in pingdotgg/t3code#16305 * fix(mobile): refresh Git status after reconnect by @juliusmarminge in pingdotgg/t3code#16329 * fix(mobile): update the iOS Git header menu when status changes by @juliusmarminge in pingdotgg/t3code#16330 * perf(mobile): reuse the settled sort when settled rows are unchanged by @juliusmarminge in pingdotgg/t3code#16369 * perf(mobile): highlight source files in small batches that keep grammar state by @juliusmarminge in pingdotgg/t3code#16729 * feat(web): quote chips show what you said about the quote by @flamboh in pingdotgg/t3code#15703 * feat(web): projectless threads show their machine in the sidebar by @flamboh in pingdotgg/t3code#17022 * fix(lineage): keep agent effort and speed after completion by @Bil0000 in pingdotgg/t3code#16925 * fix(mobile): align diff scrolling with glass headers by @juliusmarminge in pingdotgg/t3code#17085 * fix(web): workspace page headers can no longer grow past the top-bar height by @maria-rcks in pingdotgg/t3code#17086 * feat(mobile): redesign the Add environment sheet by @juliusmarminge in pingdotgg/t3code#17092 **Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261008.2801...v0.0.46-nightly.20261008.2813 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.46-nightly.20261008.2813
## What's Changed * chore(review): remove the custom Approvability check by @esthor in pingdotgg/t3code#17018 * perf(mobile): show the cached thread list sooner and stop the freeze after it by @juliusmarminge in pingdotgg/t3code#16713 * fix(server): replaying a command no longer freezes the server by @SunkenInTime in pingdotgg/t3code#17041 * fix(server): opening a long thread no longer blocks the server for a second by @SunkenInTime in pingdotgg/t3code#17029 * fix(server,web,mobile): thread links reference the thread id, not a baked title by @juliusmarminge in pingdotgg/t3code#17017 * fix(web): messages with quotes or links no longer collapse when short by @flamboh in pingdotgg/t3code#16624 * refactor(web): PR loading skeleton shares the detail panel's layout by @flamboh in pingdotgg/t3code#15583 * fix(web): PR file headers keep the file name in narrow panels by @flamboh in pingdotgg/t3code#15562 * fix(web): PR timeline no longer shifts when its scrollbar appears by @flamboh in pingdotgg/t3code#15588 * fix(server): held queued wakes no longer keep delegated tasks running by @juliusmarminge in pingdotgg/t3code#17028 * fix(server): a message sent during a rollback no longer undoes it by @t3dotgg in pingdotgg/t3code#17079 * fix(web): PR file stats ignore the hide-whitespace toggle by @flamboh in pingdotgg/t3code#16162 * perf(mobile): omit duplicated turn items from bounded thread snapshots by @juliusmarminge in pingdotgg/t3code#15385 * perf(mobile): pause elapsed-time timers on hidden thread screens by @juliusmarminge in pingdotgg/t3code#15397 * fix(mobile): pause hidden home thread list updates by @juliusmarminge in pingdotgg/t3code#15705 * fix(mobile): restore file viewer insets and glass header by @juliusmarminge in pingdotgg/t3code#17073 * fix(web): PR code toolbar no longer overlaps in narrow panels by @flamboh in pingdotgg/t3code#15561 * fix(web): PR commit menu no longer stretches across the window by @flamboh in pingdotgg/t3code#15560 * fix(web): command palette scrollbar no longer clipped at the top by @flamboh in pingdotgg/t3code#17035 * fix(web): Usage breadcrumb stays centered on small viewports by @flamboh in pingdotgg/t3code#15552 * fix(mobile): stop refreshing Git status on streamed thread updates by @juliusmarminge in pingdotgg/t3code#15893 * fix(mobile): skip move indexes for empty and single-thread sections by @juliusmarminge in pingdotgg/t3code#16115 * perf(mobile): reuse encoded rows in shell cache saves by @juliusmarminge in pingdotgg/t3code#16129 * perf(mobile): skip showcase subscriptions in normal builds by @juliusmarminge in pingdotgg/t3code#16131 * perf(mobile): remove unused Home project sorting by @juliusmarminge in pingdotgg/t3code#16177 * perf(mobile): reduce move-menu index allocations by @juliusmarminge in pingdotgg/t3code#16256 * perf(mobile): skip impossible thread-key lookups by @juliusmarminge in pingdotgg/t3code#16263 * fix(mobile): collect UI runtime garbage on iOS memory warnings by @juliusmarminge in pingdotgg/t3code#16296 * fix(mobile): stop Git sheet refresh loop by @juliusmarminge in pingdotgg/t3code#16305 * fix(mobile): refresh Git status after reconnect by @juliusmarminge in pingdotgg/t3code#16329 * fix(mobile): update the iOS Git header menu when status changes by @juliusmarminge in pingdotgg/t3code#16330 * perf(mobile): reuse the settled sort when settled rows are unchanged by @juliusmarminge in pingdotgg/t3code#16369 * perf(mobile): highlight source files in small batches that keep grammar state by @juliusmarminge in pingdotgg/t3code#16729 * feat(web): quote chips show what you said about the quote by @flamboh in pingdotgg/t3code#15703 * feat(web): projectless threads show their machine in the sidebar by @flamboh in pingdotgg/t3code#17022 * fix(lineage): keep agent effort and speed after completion by @Bil0000 in pingdotgg/t3code#16925 * fix(mobile): align diff scrolling with glass headers by @juliusmarminge in pingdotgg/t3code#17085 * fix(web): workspace page headers can no longer grow past the top-bar height by @maria-rcks in pingdotgg/t3code#17086 * feat(mobile): redesign the Add environment sheet by @juliusmarminge in pingdotgg/t3code#17092 **Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261008.2801...v0.0.46-nightly.20261008.2813 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.46-nightly.20261008.2813
Retrying a command that already ran froze my T3 server for 18 s. On a dev copy padded to the same size, the server answered nothing for 95 s. With this change the retry returns in 6 ms.
Problem
When a command arrives under an ID that already has a receipt, the orchestrator replays that command's stored events instead of running it again. Agents trigger this by retrying
delegate_taskwith the sameclientRequestId. EventSink's existing-command race takes the same path. Both read the events back withreadAgentEvents({ commandId }).That query also filters
sequence > ? AND sequence <= MAX_SAFE_INTEGERandapplication_event_version = 2. Without planner stats, SQLite picksidx_orchestration_events_application_sequence, walks every V2 event, and fetches each row to comparecommand_id. T3 never runsANALYZE, so no install has those stats; my 8.2 GB database has nosqlite_stat*tables.idx_orch_events_command_idhas existed since migration 001 and goes unused here. The SQLite client is synchronous on the main thread, so while this runs the server answers no websocket pings, runs no other requests and commits no writes.I hit this live. In my server's trace, an MCP
delegate_taskretry (...:delegate-task:nudge-pr-257-review-r1) spent 18,447 ms in onesql.executeof this query, insideorchestrationV2.dispatch.withReceipt, at 2026-10-07T21:36:12Z. My desktop client lost its connection during it.Change
readApplicationRowsnow pinsINDEXED BY idx_orch_events_command_idwhen it filters by command. The same function already pinsidx_orchestration_events_application_high_waterfor its other read, so this follows the existing pattern. The plan becomes:sequenceis the rowid, so paging withafterSequencestays a seek inside that command's rows.EventStore.readByCommandIdis the only caller that passescommandId.The new test writes 25,000 events, replays a command through
readAgentEvents, checks the returned sequences, and runsEXPLAIN QUERY PLANon the statement the store actually sent. It fails without the fix.Scope and approval
A small fix to an obvious performance bug: one query hint and one test. Related but separate: #14701 and #14703 cover the thread-list snapshot read. #14703's reader worker is opt-in per caller and doesn't reach this path.
Verification
Windows 11, Node 24.13.1, SQLite 3.51.2, rebased on main at 83a82a4.
Running server. I built a dev database with
vp run migrate-dev-db, then padded it with 880,000 synthetic V2 rows to 892,495 events and 7.5 GB, close to my live database (890,315 events, 8.2 GB). A script sentthread.metadata.updateover/ws, sent the identical command again with the samecommandId, and pinged the RPC socket every 100 ms on a second connection. Before and after differ only in this commit's source file;node --watchrestarted the server between runs.Real database, read-only (
readOnly: true), through the real store (OrchestrationEventStore.layerwithNodeSqliteClient.layer({ readonly: true })), callingreadAgentEvents({ commandId })for the command from the trace (12 events):The raw query alone, alternating order: fixed 8 ms then 2 ms; shipped 103,072 ms cold, then 37,766 ms.
Tests
With the fix reverted, the new test fails:
tsc --noEmitinapps/serverreports no errors.vp lintandvp fmt --checkon both files are clean.Not checked
delegate_taskretry on a dev server. I drove the same receipt-replay path with a clientdispatchCommandthat repeats itscommandId.Found and fixed with Claude Opus 5.5 in Claude Code (run from T3 Code), reviewed by GPT-6 Astra in Codex.
🤖 Generated with Claude Code