diff options
| author | Adam Malczewski <[email protected]> | 2026-06-02 14:45:27 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-02 14:45:27 +0900 |
| commit | 58b985b9c82c7c1acb98ac8799b8409d5c403642 (patch) | |
| tree | dcc872cf47650a10dae8f9bf4bcd03bebf6c4bf5 /packages/core/tests | |
| parent | 7c527b4d8a72159954405e720d5bf776802dc0ff (diff) | |
| download | dispatch-58b985b9c82c7c1acb98ac8799b8409d5c403642.tar.gz dispatch-58b985b9c82c7c1acb98ac8799b8409d5c403642.zip | |
fix(wake): probe with genuine Claude Code request shape so OAuth wakes succeed
The wake probe POSTed a bare { model, messages } body with no system[]
identity. Anthropic validates system[] on OAuth (Pro/Max) subscription
requests and rejects any that lack the verbatim Claude Code identity, so
every scheduled wake (and the manual Wake-now button) failed silently —
surfacing as a blank '— failed' status that then burned the retry budget.
- Add pure buildWakeProbeBody(model) in @dispatch/core mirroring a genuine
Claude Code request (billing header block + identity block + 'hi'), with
a unit test for its shape.
- wakeAllClaudeAccounts now sends that body plus the CLI session/request-id
headers, and records 'HTTP <status>: <message>' on failure so the panel
never shows a bare 'failed' and breakage stays debuggable.
Diffstat (limited to 'packages/core/tests')
| -rw-r--r-- | packages/core/tests/credentials/wake-probe.test.ts | 49 |
1 files changed, 49 insertions, 0 deletions
diff --git a/packages/core/tests/credentials/wake-probe.test.ts b/packages/core/tests/credentials/wake-probe.test.ts new file mode 100644 index 0000000..253efec --- /dev/null +++ b/packages/core/tests/credentials/wake-probe.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it, vi } from "vitest"; + +// `claude.ts` transitively imports `db/index.js`, whose top-level +// `import { Database } from "bun:sqlite"` can't resolve under vitest's Node +// runtime. Stub the db module — `buildWakeProbeBody` never touches it. +vi.mock("../../src/db/index.js", () => ({ + getDatabase: vi.fn(() => { + throw new Error("db not available in this test"); + }), +})); + +const { buildWakeProbeBody } = await import("../../src/credentials/claude.js"); + +const IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude."; + +describe("buildWakeProbeBody", () => { + it("targets the requested model with a tiny token budget", () => { + const body = buildWakeProbeBody("claude-3-5-haiku-20241022"); + expect(body.model).toBe("claude-3-5-haiku-20241022"); + expect(body.max_tokens).toBe(16); + }); + + it("emits a Claude-Code-shaped system[]: billing first, identity second", () => { + const body = buildWakeProbeBody("claude-3-5-haiku-20241022"); + expect(body.system).toHaveLength(2); + + // system[0] is the billing header line (no cache_control on a probe). + expect(body.system[0]).toEqual({ + type: "text", + text: expect.stringMatching(/^x-anthropic-billing-header: /), + }); + expect(body.system[0]).not.toHaveProperty("cache_control"); + + // system[1] is the VERBATIM Claude Code identity string. Anthropic + // rejects OAuth (Pro/Max) requests whose system[] lacks this. + expect(body.system[1]).toEqual({ type: "text", text: IDENTITY }); + }); + + it("carries a single short user message", () => { + const body = buildWakeProbeBody("claude-3-5-haiku-20241022"); + expect(body.messages).toEqual([{ role: "user", content: "hi" }]); + }); + + it("is deterministic for a given model (pure)", () => { + const a = buildWakeProbeBody("claude-3-5-haiku-20241022"); + const b = buildWakeProbeBody("claude-3-5-haiku-20241022"); + expect(a).toEqual(b); + }); +}); |
