summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 14:51:16 +0900
committerAdam Malczewski <[email protected]>2026-06-02 14:51:16 +0900
commit40b0b6a23a5cbd494f9956315c2e424d16edb282 (patch)
tree03bfb2833cc0c5f5ffdf6d7687a921ee933dd20e
parentd022877e3a4c954adafce3894f70c176dee76433 (diff)
downloaddispatch-40b0b6a23a5cbd494f9956315c2e424d16edb282.tar.gz
dispatch-40b0b6a23a5cbd494f9956315c2e424d16edb282.zip
docs: handoff for cr/claude-reset-fix (wake probe body fix)
-rw-r--r--HANDOFF.md118
1 files changed, 64 insertions, 54 deletions
diff --git a/HANDOFF.md b/HANDOFF.md
index 5755279..a8fa7f6 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -1,63 +1,73 @@
-# Handoff — sb/status-bar
+# Handoff — cr/claude-reset-fix
-Add a status bar beneath the chat input that houses the send button and shows
-generation status + context-window usage.
+## Objective
+Make the Claude Wake Schedule ("Claude reset") system work reliably end to end.
+
+## Root cause found
+The two issues named in the task brief (toggle endpoint ignoring client intent;
+server-side request reordering desyncing the UI) were **already fixed** in this
+worktree from prior rounds — the toggle endpoint requires an explicit
+`action: 'on' | 'off'`, and the frontend serializes mutations behind a global
+`pendingHour` lock (verified: tests + code present, suite green).
+
+The *actual* live failure (reproduced by the user: `✗ Last wake 4 min ago —
+failed`, blank reason, then `Retrying (6 left…)`) was in the **wake probe
+itself**, not the scheduler or UI:
+
+`wakeAllClaudeAccounts()` POSTed a bare body
+`{ model, max_tokens, messages: [{role:"user",content:"hi"}] }` with **no
+`system[]`**. These accounts are OAuth (Claude Pro/Max) subscriptions, and
+Anthropic validates `system[]` on Claude-Code-billed OAuth requests — it
+rejects (401/403) any request whose system block lacks the verbatim Claude Code
+identity string. So **every** scheduled wake and the manual "Wake now" button
+failed. The old code recorded only `ok: res.ok` with no error text, surfacing as
+a blank "— failed" that then burned the 6×5-min retry budget.
## Files changed
-- `packages/frontend/src/lib/components/ChatInput.svelte` — restructured into
- **two stacked bars** (wrapped in `flex flex-col`):
- - **Top bar:** the existing auto-resizing textarea + a single, fixed-width
- (`w-20`) send/stop button that morphs in place so the layout never shifts.
- Three states:
- - not generating → `btn-primary` **Send**, disabled when the box is empty
- (unchanged look).
- - generating + empty box → **Stop** (`btn-error btn-outline`, spinner +
- "Stop"), calls `tabStore.stopGeneration(tabId)`.
- - generating + text in box → enabled **Send** (queues the message via
- `tabStore.sendMessage`).
- - **Bottom bar:** agent status icon on the left (✓ idle / spinner running /
- ✗ error), a context-window fill `progress` bar filling the middle, and a
- compact token count + percent on the right (e.g. `12.3k / 200k · 6.1%`).
- When the model's max context is unknown the bar renders inert/disabled
- (`opacity-40`, no value) and the right side shows the bare current token
- count with no percent. Before the first response it reads `— tokens`.
-- `packages/frontend/src/App.svelte` — pass the already-computed `contextLimit`
- into `<ChatInput {contextLimit} />` (same value handed to the sidebar).
+- **`packages/core/src/credentials/claude.ts`** — new pure
+ `buildWakeProbeBody(model)` that mirrors a genuine Claude Code request:
+ `system: [{billing-header}, {identity}]` + `messages:[{role:"user",content:"hi"}]`,
+ `max_tokens: 16`. Reuses existing `buildBillingHeaderValue` + `SYSTEM_IDENTITY`.
+- **`packages/core/src/credentials/index.ts`** — export `buildWakeProbeBody`
+ (reachable as `@dispatch/core`).
+- **`packages/api/src/routes/models.ts`** — `wakeAllClaudeAccounts` now sends
+ `buildWakeProbeBody(WAKE_PROBE_MODEL)` plus the CLI session headers
+ (`X-Claude-Code-Session-Id`, `x-client-request-id`), and on `!res.ok` records
+ `HTTP <status>: <message>` via new `describeFailedResponse(res)` so the panel
+ never shows a bare "failed" again and breakage stays debuggable.
+- **`packages/core/tests/credentials/wake-probe.test.ts`** — new: 4 unit tests
+ asserting the probe body shape (model/tokens, billing-first/identity-second
+ system[], single "hi" user message, determinism).
## Public surface changed
-- `ChatInput.svelte` gained one optional prop: `contextLimit?: number | null`
- (defaults to `null`). No other exported API/type changes.
-- Reuses the shared `computeContextUsage()` helper (`lib/context-window.ts`) and
- the same fill-color thresholds (calm→warning→danger) as the Context Window
- sidebar panel, so the two displays always agree. Compact `k`/`M` token
- formatting is local to `ChatInput` (the sidebar keeps full `toLocaleString`).
-
-## Design decisions (agreed with requester)
-- Two stacked bars; send button kept (not dropped) for discoverability.
-- Send and Stop are the **same button** at a fixed width — no layout jump.
-- Bottom bar: status icon left; context number + percent right; progress bar
- fills the remaining width; disabled/inert bar when the model has no known max.
-- Token format: compact (`12.3k`) for the slim bar.
-
-## Verification status — PASS
-- `bun run check` (biome): **PASS** — "Checked 163 files… No fixes applied."
-- `bun run test` (vitest): **PASS** — 35 files, 552 tests.
-- `bun run --cwd packages/frontend typecheck` (svelte-check): **PASS** — 0
- errors, 0 warnings.
-- Re-verified all three after `git merge --no-edit dev` — still all-green.
-- Note: `bun install` was required first; deps were not present in the worktree.
+- New export `@dispatch/core` → `buildWakeProbeBody(model: string)`.
+- No API route signatures changed. `POST /models/wake`, `POST
+ /models/wake-schedule/toggle`, `GET /models/wake-schedule` request/response
+ shapes are unchanged. The only externally visible behavior change: failed
+ wakes now carry a descriptive `error` string (`HTTP <status>: <message>`)
+ instead of an empty one, which the panel already renders.
+- DB schema: unchanged.
+
+## Verification
+- `bun run check` (biome) → clean, 164 files.
+- `bun run test` (vitest) → **568 passed** (post-merge with dev; +4 from this
+ branch's new probe-body tests).
+- `tsc -p packages/core` and `tsc -p packages/api` → exit 0.
+- `svelte-check` (frontend) → 0 errors, 0 warnings.
## Published
-- Yes. Feature commit `2756730`, merged `dev` down (`f0207a7`, clean merge —
- picked up another agent's CacheRatePanel/KeyUsage changes + their HANDOFF.md),
- pushed fast-forward `3f0bfe7..f0207a7 → dev`.
-- This HANDOFF.md was rewritten from the incoming `m1/minor-fixes` handoff (that
- content remains preserved in git history on its own merge).
+Yes. Merged `dev` down into `cr/claude-reset-fix` (clean merge, no conflicts),
+re-verified all-green, then `git push . HEAD:dev` (fast-forward, accepted).
## Assumptions / known gaps
-- User visually confirmed the UI before merge ("sweet, merge it in").
-- No component-render tests were added for the Svelte markup, consistent with
- the existing repo (these panels have no render tests). Logic is exercised
- indirectly via the unchanged `computeContextUsage` unit tests.
-- Context usage reflects the most recent turn (`last.inputTokens +
- last.outputTokens`) — identical semantics to the sidebar panel.
+- **Live API not testable from the agent sandbox.** The fix is verified offline
+ (body shape + headers match the real `transformClaudeOAuthBody` provider path
+ and unit tests). The actual "200 OK wake" requires real OAuth credentials and
+ was deferred to the user-test gate. If a probe still fails, the panel now shows
+ a concrete `HTTP <status>: <message>` reason to diagnose from.
+- **Probe model** is hardcoded `claude-3-5-haiku-20241022` (`WAKE_PROBE_MODEL`
+ in `models.ts`). Cheap/small by design — only needs to register activity.
+- **Deferred (unchanged design trade-offs, pre-existing):** DST drift on the
+ 24h advance; no background snapshot polling (UI may show stale "Retrying…"
+ until next user interaction); retry storm re-probes already-succeeded accounts.
+ None of these block reliable wakes; the probe fix addresses the live failure.