Skip to content

fix(server): replaying a command no longer freezes the server - #17041

Merged
t3dotgg merged 1 commit into
pingdotgg:mainfrom
SunkenInTime:fix/command-replay-scan
Oct 8, 2026
Merged

t3dotgg merged 1 commit into
pingdotgg:mainfrom
SunkenInTime:fix/command-replay-scan

Conversation

@SunkenInTime

@SunkenInTime SunkenInTime commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

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.

Replaying a command: main blocks the server for 95 s, this PR for 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_task with the same clientRequestId. EventSink's existing-command race takes the same path. Both read the events back with readAgentEvents({ commandId }).

That query also filters sequence > ? AND sequence <= MAX_SAFE_INTEGER and application_event_version = 2. Without planner stats, SQLite picks idx_orchestration_events_application_sequence, walks every V2 event, and fetches each row to compare command_id. T3 never runs ANALYZE, so no install has those stats; my 8.2 GB database has no sqlite_stat* tables. idx_orch_events_command_id has 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_task retry (...:delegate-task:nudge-pr-257-review-r1) spent 18,447 ms in one sql.execute of this query, inside orchestrationV2.dispatch.withReceipt, at 2026-10-07T21:36:12Z. My desktop client lost its connection during it.

Change

readApplicationRows now pins INDEXED BY idx_orch_events_command_id when it filters by command. The same function already pins idx_orchestration_events_application_high_water for its other read, so this follows the existing pattern. The plan becomes:

SEARCH orchestration_events USING INDEX idx_orch_events_command_id (command_id=? AND rowid>? AND rowid<?)

sequence is the rowid, so paging with afterSequence stays a seek inside that command's rows. EventStore.readByCommandId is the only caller that passes commandId.

The new test writes 25,000 events, replays a command through readAgentEvents, checks the returned sequences, and runs EXPLAIN QUERY PLAN on 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 sent thread.metadata.update over /ws, sent the identical command again with the same commandId, and pinged the RPC socket every 100 ms on a second connection. Before and after differ only in this commit's source file; node --watch restarted the server between runs.

replayed dispatch longest wait for a ping reply
before 95,090 ms 95,332 ms (936 pings unanswered)
after 6 ms 435 ms

Real database, read-only (readOnly: true), through the real store (OrchestrationEventStore.layer with NodeSqliteClient.layer({ readonly: true })), calling readAgentEvents({ commandId }) for the command from the trace (12 events):

  • before: 31,677 ms
  • after: 22 ms (73 ms on a cold first run)

The raw query alone, alternating order: fixed 8 ms then 2 ms; shipped 103,072 ms cold, then 37,766 ms.

Tests

$ cd apps/server
$ vp test run src/persistence/OrchestrationEventStore.sequence.test.ts src/persistence/OrchestrationEventStore.test.ts
 Test Files  2 passed (2)
      Tests  12 passed (12)

With the fix reverted, the new test fails:

AssertionError: expected 'SEARCH orchestration_events USING IND…' to match /SEARCH orchestration_events USING INDEX idx_orch_events_command_id \(command_id=\?/

tsc --noEmit in apps/server reports no errors. vp lint and vp fmt --check on both files are clean.

Not checked

  • macOS and Linux. The plan choice comes from SQLite, not the OS, but I only ran on Windows.
  • A full MCP delegate_task retry on a dev server. I drove the same receipt-replay path with a client dispatchCommand that repeats its commandId.
  • The padded dev database also showed event-loop stalls of 3 to 9 s that had nothing to do with the replay, likely other reads walking the synthetic rows. That's why I compare the two runs rather than reading the ping numbers on their own.

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

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Oct 8, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 11b6fbe

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.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 25486674-02b0-4fea-8ff6-18d06fb41fe3
📥 Commits

Reviewing files that changed from the base of the PR and between 11b6fbe and 074c357.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 82ee77d7-3cbc-44de-a733-bd40887a5b39
📥 Commits

Reviewing files that changed from the base of the PR and between 83a82a4 and 11b6fbe.

📒 Files selected for processing (2)
  • apps/server/src/persistence/OrchestrationEventStore.sequence.test.ts
  • apps/server/src/persistence/OrchestrationEventStore.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

Command-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.

Changes

Command-Filtered Event Reads

Layer / File(s) Summary
Command index selection and validation
apps/server/src/persistence/OrchestrationEventStore.ts, apps/server/src/persistence/OrchestrationEventStore.sequence.test.ts
readApplicationRows forces the command-ID index when a command filter is present. A persistence test checks the returned sequences and confirms the query uses that index.

Priority: ⬆️ High

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to 11b6f

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)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main fix: preventing command replay from freezing the server. It is concise and uses a conventional commit format.
Description check ✅ Passed The description covers the problem, change, scope and approval rationale, verification results, test commands, observed performance data, and untested platforms or scenarios. It provides sufficient de…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

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>
@t3dotgg
t3dotgg force-pushed the fix/command-replay-scan branch from 11b6fbe to 074c357 Compare October 8, 2026 02:32
@t3dotgg
t3dotgg merged commit 9fba209 into pingdotgg:main Oct 8, 2026
28 checks passed
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Oct 8, 2026
## 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
github-actions Bot added a commit to davidvanderklay/t3code-flake that referenced this pull request Oct 8, 2026
## 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS 0-9 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants