fix(server): working timers no longer reset on every background wake#15029
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
Added 1 commit
- fix(server): working timers no longer reset on every background wake6b30d45
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.
Thread transfer impact
ā Thread transfer remains within every enforced ceiling.
| Provider | Metric | Main baseline | This PR | Impact | PR ceiling | |
|---|---|---|---|---|---|---|
| Codex | Total thread wire | 4.9 KiB | 4.9 KiB | 0 B (0.0%) | 6.8 KiB | ā |
| Codex | Thread snapshot wire | 3.7 KiB | 3.7 KiB | 0 B (0.0%) | 4.9 KiB | ā |
| Codex | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | 0 B (0.0%) | 2.0 KiB | ā |
| Codex | Live turn WebSocket decoded | 20.4 KiB | 20.4 KiB | 0 B (0.0%) | 29.3 KiB | ā |
| Codex | Live turn messages | 2 | 2 | 0 (0.0%) | 8 | ā |
| Claude | Total thread wire | 4.9 KiB | 4.9 KiB | 0 B (0.0%) | 6.8 KiB | ā |
| Claude | Thread snapshot wire | 3.7 KiB | 3.7 KiB | 0 B (0.0%) | 4.9 KiB | ā |
| Claude | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | 0 B (0.0%) | 2.0 KiB | ā |
| Claude | Live turn WebSocket decoded | 20.7 KiB | 20.7 KiB | 0 B (0.0%) | 29.3 KiB | ā |
| Claude | Live turn messages | 1 | 1 | 0 (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.