## Evidence

Fix for #20249: a Claude thread waiting on a `ScheduleWakeup` timer now stays in Working instead of landing in the inbox.

**Outside the PR title's scope (read first):** after **Stop** on the "Waiting on background task" banner (F5), the finished turn's "Worked for 5.7s ›" header re-rendered as a bare `ScheduleWakeup` tool row. The diff is server-only, so this most likely comes from the existing Stop path (`interrupt` of the latest settled run), not from new client code. There is no before for this, because the banner doesn't appear on main. Cause not investigated.

- Before = `ed3f965e07` (merge-base on origin/main), After = `952d52ee2f` (`fix/issue-20249`). Both sides ran from the same worktree and the same seeded `.t3` (one DB, with no re-seed between sides).
- Environment, for every capture: T3 preview browser (Chromium 154, headless, Linux x86_64), no device emulation, DPR 2, viewport 1440×900 (F8: 390×844), `prefers-color-scheme` set through `preview_set_appearance`. The 1440 stills are saved at 1280×800 because the preview tool downscales them. The 390 stills are 780×1688 (DPR 2). Recordings are 2880×1800.
- Model: Claude Sonnet 5.5. Prompt (B0, F1, F3, F5, F7, F8): "Use the ScheduleWakeup tool with delaySeconds 120 and prompt 'Reply with the word WOKE and nothing else.' Do not do anything else, then end your turn with one short sentence."
- State read-outs come from the worktree's `statev2.sqlite`, opened read-only: the provider thread's `pendingBackgroundTasks` plus the run list.
- Diffs come from ImageMagick `compare -metric AE`. Diff0 uses zero fuzz and Diff10 uses `-fuzz 10%`. Changed pixels are red over a dimmed base (that is the amplification).

### B0 — baseline on main: the turn that schedules a wakeup goes to the inbox (recording, 1440, dark)

Steps (UTC): 13:22:54.5 recording starts on a fresh draft (Working section ON) → 13:22:56 Enter → 13:23:05.3 turn completes with `pendingBackgroundTasks= []` and run 1 `completed` → the row stays in the inbox list with no Working section and no banner. The agent says it fires at 13:26:00 → 13:23:26 recording stops.

[B0-before-turn-end-1440-dark.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/336bee5d7459a15a/B0-before-turn-end-1440-dark.mp4)

Frame at +25 s: ![B0-frame-after-turn-end.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/5bc21775a3bfac8a/B0-frame-after-turn-end.png)

### F1 — pending wakeup, Working shelf ON: the row shows "Waiting" under Working (1), and the banner shows "Waiting on background task Scheduled wakeup · Stop"

Recording (dark, 1440; the same recording continues into F3). Steps (UTC): 13:25:41.2 recording starts → 13:25:42 Enter → 13:25:48.9 turn completes with `pendingBackgroundTasks=[{taskId:'claude-scheduled-wakeup', kind:'background_task', description:'Scheduled wakeup'}]` → the row moves to "Working (1)" with status "Waiting", and the banner appears. The agent says it fires at 13:28:00.

[F1-F3-after-turn-end-wait-wake-1440-dark.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/fce6c7060e3a17dc/F1-F3-after-turn-end-wait-wake-1440-dark.mp4)

Stills taken right after the turn ended. Before = B0 thread, After = F1 thread (dark) or F5 thread (light, captured before its Stop).

| 1440×900, **dark** | Before `ed3f965e07` | After `952d52ee2f` | Diff0 (AE 57105) | Diff10 (AE 33550) |
|---|---|---|---|---|
| after turn end | ![before-after-turn-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/5c4e92ce635a8e3c/before-after-turn-1440-dark.png) | ![F1-after-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c22ce94e1cc53d1f/F1-after-1440-dark.png) | ![diff0-F1-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/a2cc37ad122b6780/diff0-F1-1440-dark.png) | ![diff10-F1-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ffb0a164ab5bb9a3/diff10-F1-1440-dark.png) |

| 1440×900, **light** | Before `ed3f965e07` | After `952d52ee2f` | Diff0 (AE 66364) | Diff10 (AE 12952) |
|---|---|---|---|---|
| after turn end | ![before-after-turn-1440-light.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c27d5325694fe841/before-after-turn-1440-light.png) | ![F1-after-1440-light.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/3d681ab86a2335bf/F1-after-1440-light.png) | ![diff0-F1-1440-light.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ca930927e6fd0611/diff0-F1-1440-light.png) | ![diff10-F1-1440-light.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/04096af8c1cb577b/diff10-F1-1440-light.png) |

### F2 — pending wakeup, Working shelf OFF: the row stays in its list with status "Waiting", and the banner is unchanged (still, 1440, dark)

| 1440×900, **dark**, Working section off | Before `ed3f965e07` | After `952d52ee2f` | Diff0 (AE 35746) | Diff10 (AE 11849) |
|---|---|---|---|---|
| after turn end | ![F2-before-shelf-off-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c4ba75d7c931c805/F2-before-shelf-off-1440-dark.png) | ![F2-after-shelf-off-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/25f7ca084350134b/F2-after-shelf-off-1440-dark.png) | ![diff0-F2-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/a0751f3161600f4b/diff0-F2-1440-dark.png) | ![diff10-F2-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e28c4c914f73e3ef/diff10-F2-1440-dark.png) |

Before: the row is in the inbox and shows the relative time, with no status and no banner. After: the row shows "Waiting 18s", and the banner reads "Waiting on background task Scheduled wakeup · Stop".

### F3 — the wake fires with no reschedule: the wake turn runs, then the thread settles to the inbox (recording continues from F1)

Steps (UTC), same recording as F1: 13:25:48.9 → 13:28:00 the row sits under Working/Waiting with the banner up → 13:28:00.98 the provider goes `active` and run 2 starts → 13:28:04.57 run 2 `completed` with `pendingBackgroundTasks= []` → the banner disappears, "WOKE" renders, the row leaves Working for the inbox and the Working section disappears → 13:28:20 recording stops. There was no after-side before for F3 to compare against: main never shows the hold.

[F1-F3-after-turn-end-wait-wake-1440-dark.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/fce6c7060e3a17dc/F1-F3-after-turn-end-wait-wake-1440-dark.mp4)

After the wake (`952d52ee2f`, 1440, dark): ![F3-after-wake-settled-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/76bf06f76f951cb2/F3-after-wake-settled-1440-dark.png)

### F5 — Stop from the banner: the banner disappears, the row leaves Working, and no wake turn runs (recording, 1440, dark)

Steps (UTC): this is the second F1 thread, which the agent scheduled for 13:31:00. 13:29:23.1 recording starts with the row under Working/Waiting and the banner up → 13:29:27.6 click **Stop** in the banner → 13:29:28.24 the provider thread is `idle` with `pendingBackgroundTasks= []` and the banner and Working section disappear → 13:31:00 is the scheduled time, and no run 2 starts → 13:32:09 read-out still shows only run 1 `completed` → 13:32:12 recording stops (scheduled time + 72 s). No WOKE turn appeared.

[F5-after-stop-through-scheduled-plus-60s-1440-dark.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/8ac14381ce890e96/F5-after-stop-through-scheduled-plus-60s-1440-dark.mp4)

At 13:32:12 (`952d52ee2f`): ![F5-after-stopped-at-1332-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/9795689a1d35bae9/F5-after-stopped-at-1332-1440-dark.png)

### F4 (optional) — the wake fires and reschedules, so the thread returns to Waiting (recording, 1440, dark)

Prompt: wake 1 calls ScheduleWakeup again for 120 s, and wake 2 replies "DONE". Steps (UTC): 13:36:08.4 recording starts → 13:36:10.9 Enter → 13:36:17.2 turn 1 completes with the wakeup pending (Waiting, banner up; fires at 13:39:00) → 13:39:00 wake 1 runs and schedules wake 2 for 13:42:00. By 13:39:04 run 2 is `completed` and the wakeup is **still pending**, so the banner stays and the row stays in Working → 13:42:00 wake 2 runs and says "DONE" → 13:42:02 run 3 `completed`, `pendingBackgroundTasks= []`, and the thread settles → 13:42:12 recording stops.

[F4-after-wake-reschedules-then-done-1440-dark.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/de44ebc73de532a4/F4-after-wake-reschedules-then-done-1440-dark.mp4)

| `952d52ee2f`, 1440, dark | Between wakes (13:40:30 frame) | Final (13:42:12) |
|---|---|---|
| F4 | ![F4-frame-between-wakes-1340-30.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/0cf32168c96a77a6/F4-frame-between-wakes-1340-30.png) | ![F4-after-final-settled-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/155bc83e550277be/F4-after-final-settled-1440-dark.png) |

### F6 (optional) — the agent cancels with `stop: true` in the same turn: the thread goes to the inbox right after the turn with no banner, as in B0 (recording, 1440, dark)

Steps (UTC): 13:35:30.7 recording starts → 13:35:33.2 Enter → 13:35:46.2 turn completes with `pendingBackgroundTasks= []`. The agent says "I scheduled the 120-second wakeup and then cancelled it, so nothing is pending." There is no banner and no Working section → 13:35:59 recording stops.

[F6-after-agent-cancels-1440-dark.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/3af2d262e569a725/F6-after-agent-cancels-1440-dark.mp4)

![F6-after-agent-cancels-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/879b42704c4fcba1/F6-after-agent-cancels-1440-dark.png)

### F7 (optional) — the user sends a message while the wakeup is pending: that turn settles, the thread returns to Waiting, and the wake still fires (recording, 1440, dark)

Steps (UTC): the thread's wakeup is due at 13:35:00. 13:33:10.6 recording starts → 13:33:13.9 send "what time is it?" → 13:33:13.9 run 2 is running and the hold is still listed → 13:33:21.5 run 2 `completed` with the hold **still pending**, so the banner comes back and the row returns to Waiting → 13:35:00 the wake runs (run 3) and says WOKE → 13:35:02.98 `pendingBackgroundTasks= []` and the thread settles → 13:35:19 recording stops.

[F7-after-user-turn-while-pending-then-wake-1440-dark.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/9fbe1389910d4cc0/F7-after-user-turn-while-pending-then-wake-1440-dark.mp4)

Final: ![F7-after-wake-settled-1440-dark.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/9f45858efec565f9/F7-after-wake-settled-1440-dark.png)

### F8 (optional) — narrow web at 390×844, dark (`952d52ee2f`, same thread as F7, before its user message)

| Chat (banner truncates to "Waiting on background task…" ⓘ Stop) | Sidebar sheet ("Working (1)" with the row "Waiting 19s") |
|---|---|
| ![F8-after-390-dark-chat.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/9e4678c26c6bd3ae/F8-after-390-dark-chat.png) | ![F8-after-390-dark-sidebar.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/a0bc79ea8738b211/F8-after-390-dark-sidebar.png) |

### Unverified

- **M1, M2 (mobile list, detail banner and sheet):** not captured. `device_list` reports "Agent device access is turned off for this environment", and this Linux VM has no iOS Simulator. No mobile code changed. Mobile uses the same shared derivation, which the web captures above exercise.

### What else changed (every visible difference in the Before|After pairs)

1. **Intended:** a "Working (1)" section appears at the bottom of the sidebar with the thread row showing "Waiting Ns" (F1). With the shelf off, the row shows "Waiting Ns" in place in the main list (F2).
2. **Intended:** a chat banner appears above the composer: "Waiting on background task Scheduled wakeup · Stop" (F1, F2).
3. **Intended (follows from 1):** with the shelf on, the after thread is no longer the top inbox row, so the inbox list shifts.
4. **Environment, not the PR:** the right panel shows "Select ref / Commit / Changes +0 −0" before (worktree on a detached HEAD) and "fix/issue-20249 / Push & create PR / Changes +522 −2" after (worktree on the fix branch).
5. **Environment, not the PR:** each side has a different thread title, assistant text, "Worked for N s" and wake time, because these were live Claude runs. The after-side sidebar also lists the earlier before-side thread ("Schedule a Wakeup Reply, 3m"), because both sides shared one DB. The light After still is from the F5 thread ("Schedule a Wakeup"), not the F1 thread, because I took it on a second thread so the F1→F3 dark recording stayed unbroken.
6. **Outside scope, after only:** after Stop (F5), the finished turn's "Worked for 5.7s ›" header re-rendered as a `ScheduleWakeup` tool row. See the note at the top.
