# #20249 proof matrix — pass 1 (branch fix/issue-20249 @ 952d52ee2f)

The change is server-only. While a Claude `ScheduleWakeup` is pending, the provider thread's `pendingBackgroundTasks` includes
`{taskId:"claude-scheduled-wakeup", kind:"background_task", description:"Scheduled wakeup"}`.
`backgroundWorkHoldsCompletion` (packages/shared/src/orchestrationV2PendingBackgroundWork.ts) counts `background_task` as holding, and every client derives its state from that:

| Consumer | Code | Effect while a wakeup is pending |
|---|---|---|
| Web sidebar Working shelf vs inbox | client-runtime `shellRuntime` (models.ts:216) parks runtime at `idle`; `isThreadWorking` (threadInbox.ts:15) counts `idle` as working | the thread sits under "Working" instead of the inbox |
| Web thread row status | Sidebar.logic.ts:1109 | "Waiting" (muted dot, no pulse) |
| Web chat banner | useThreadLifecycle.tsx:388 (shown only when `!isWorking`), ThreadActivityBanners.tsx:94 | "Waiting on background task Scheduled wakeup", with a pulsing dot and a Stop control |
| Stop | client-runtime commands.ts:818 interrupts the latest settled run when pending work is non-empty | adapter closes the CLI and the hold drops |
| Agent awareness (desktop/mobile notifications) | shared agentAwareness.ts:73,123 | phase `running` instead of `completed`; no "Review the completed task" notice until the wake turn ends |
| Mobile thread list | thread-list-v2-items.tsx `waiting` | "Waiting" (circle.dashed.inset.filled) |
| Mobile detail and sheet | ThreadDetailScreen.tsx:675, ThreadBackgroundWorkSheet.tsx:41 | same `presentPendingBackgroundWork` title, sheet lists "Scheduled wakeup" |

## Fixtures

| ID | Fixture | Data / how to seed | Affected by diff | Capture | Status |
|---|---|---|---|---|---|
| B0 | **Baseline (main)**: the turn that scheduled a wakeup ends, so the thread goes to the inbox | main build, same prompt as F1 | before-side | recording, desktop 1440, dark | MISSING (lead observed it; no capture yet) |
| F1 | **Pending wakeup, Working shelf ON**: the row shows "Waiting" under Working (1), and the chat banner shows "Waiting on background task Scheduled wakeup · Stop" | branch dev server, `migrate-dev-db`, Settings > General Working section on, new Claude Sonnet 5.5 thread: "Call ScheduleWakeup with delaySeconds 120, prompt 'say WOKE', then end your turn." | YES | recording of the turn ending and the move into Working (not inbox); plus stills at 1440 in dark and light | MISSING |
| F2 | **Pending wakeup, Working shelf OFF**: the row stays in its project list with status "Waiting", and the banner is unchanged | F1 with the setting off | YES (status pill: Waiting instead of Completed or unread) | still, 1440 dark | MISSING |
| F3 | **Wake fires, no reschedule**: the wake turn runs ("Working", banner hidden), then the thread settles to the inbox as completed | F1, wait ~2–3 min | YES (the release path) | recording continuous from F1 through the wake to the inbox | MISSING |
| F4 | **Wake fires and reschedules**: the thread returns to the F1 state | prompt the wake to call ScheduleWakeup again once (for example "on wake, schedule one more 120 s wakeup, then on the second wake say DONE") | YES | recording, which can extend F3's | MISSING (optional if F3 plus unit tests are accepted; it is the /loop case) |
| F5 | **Stop from the banner**: the banner disappears, the row leaves Working, and no wake turn ever runs | F1, click Stop in the banner, then wait past the scheduled time | YES | recording through scheduledFor+60 s, showing no WOKE turn | MISSING |
| F6 | **Agent cancels (`ScheduleWakeup stop:true`) in the same turn**: the thread goes to the inbox right after the turn, with no banner | prompt: "schedule a 120 s wakeup, then cancel it with stop:true" | YES (must look like B0) | still or short recording | MISSING (lower priority; covered by a unit test) |
| F7 | **User sends a message while the wakeup is pending**: that turn runs and settles, the thread returns to Waiting, and the wake still fires later | F1, send "what time is it?" before it fires | YES (hold survives a user turn) | recording | MISSING (lower priority; covered by a unit test) |
| F8 | **Narrow web (390)**: sidebar sheet row status and banner wrap | F1 at width 390 | Same as F1; existing layout | still, 390 dark | OPTIONAL |
| M1 | **Mobile thread list**: the thread shows "Waiting" in the working group | iOS sim pointed at the branch dev server (pairing URL), live F1 thread. The static showcase fixture cannot express this: `scripts/mobile-showcase*.ts` seeds no provider-thread `pendingBackgroundTasks`, so the state only comes from a live server | indirect (no mobile code changed; shared derivation) | still | OPTIONAL: recommended as one still because the same derivation feeds it, but not blocking |
| M2 | **Mobile detail banner and ThreadBackgroundWorkSheet**: shows "Scheduled wakeup" | same as M1 | indirect | still | OPTIONAL |

Minimum required set: B0 (recording), F1 (recording plus dark and light stills), F2 (still), F3 (recording), F5 (recording).
F4, F6 and F7 are covered by adapter unit tests; capture them if time allows. F8, M1 and M2 are optional.

Operational notes: do not run vitest in the worktree while capturing. Writes under `packages/*/node_modules/.vite-temp` restart the dev server, which kills the live Claude process and with it the pending wakeup, so F1, F3 and F5 would be invalidated. One live F1 thread can feed F1, then F3; use a second F1 thread for F5.
