fix(server): working timers no longer reset on every background wake#15029

merged1
main
t3code/fix-working-timer-calculation
openedOct 3, 2026byTtheo
View on GitHubTwo-way sync enabledPR last imported 22d ago

The "Working for" timers (sidebar rows, chat header, mobile) kept resetting on threads that had been running for 30+ minutes. Each background wake (a background command or task finishing, a delegated task result, a restart continuation) starts a new run, and the timers counted from the current run's start. One real thread had 8 wakes in a row, so its timer reset 8 times.

Now a wake run carries workStartedAt: the work start of the run that last started before it. The decider stamps it when the wake run starts, not when it queues, so queued prompts ahead of it and delegated results that jump the queue both resolve correctly. The server shell SQL, the shell-from-projection path, and client-runtime deriveThreadRuntime all read it through one helper, orchestrationV2RunWorkStartedAt. Prompts, queued messages, MCP sends, and scheduled tasks still start a new count. The shell field name does not change, so older web and mobile builds also get the fix. Existing runs are not migrated, so threads that are mid-work keep the old value until their next prompt.

Proof: new decider test drives real queue promotions (wake ahead of a queued prompt, wake after a late-starting prompt), plus shell SQL/projection parity and client-runtime tests; typecheck clean for contracts, client-runtime, server, web; related server suites (159 tests) pass.

Reviewed with sol-loop: 2 rounds with gpt-6.1-sol on default effort.

Created with Claude Opus 5.5 in Claude Code.

šŸ¤– Generated with Claude Code

Checks

26 successful Ā· 10 skipped
All checks passed
6b30d45

Added 1 commit

  • fix(server): working timers no longer reset on every background wake6b30d45
Synced from GitHub

Approvability

Verdict: Approved at 6b30d45

Macroscope's review found this PR approvable — This is a focused bug fix that preserves existing execution behavior while making background-wake timers retain the start of the work they continue. The optional field has backward-compatible fallbacks, server/client timer paths are aligned, and targeted tests cover queueing and wake scenarios.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

āœ… Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire4.9 KiB4.9 KiB0 B (0.0%)6.8 KiBāœ…
CodexThread snapshot wire3.7 KiB3.7 KiB0 B (0.0%)4.9 KiBāœ…
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiBāœ…
CodexLive turn WebSocket decoded20.4 KiB20.4 KiB0 B (0.0%)29.3 KiBāœ…
CodexLive turn messages220 (0.0%)8āœ…
ClaudeTotal thread wire4.9 KiB4.9 KiB0 B (0.0%)6.8 KiBāœ…
ClaudeThread snapshot wire3.7 KiB3.7 KiB0 B (0.0%)4.9 KiBāœ…
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiBāœ…
ClaudeLive turn WebSocket decoded20.7 KiB20.7 KiB0 B (0.0%)29.3 KiBāœ…
ClaudeLive turn messages110 (0.0%)8āœ…

Baseline: 9333509 Ā· PR result: 6b30d45 Ā· Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 106.1 KiB
  • Claude decoded thread snapshot: 106.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.