summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 14:45:27 +0900
committerAdam Malczewski <[email protected]>2026-06-02 14:45:27 +0900
commit58b985b9c82c7c1acb98ac8799b8409d5c403642 (patch)
treedcc872cf47650a10dae8f9bf4bcd03bebf6c4bf5 /packages/core/src
parent7c527b4d8a72159954405e720d5bf776802dc0ff (diff)
downloaddispatch-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/src')
-rw-r--r--packages/core/src/credentials/claude.ts38
-rw-r--r--packages/core/src/credentials/index.ts1
2 files changed, 39 insertions, 0 deletions
diff --git a/packages/core/src/credentials/claude.ts b/packages/core/src/credentials/claude.ts
index 168d544..432e403 100644
--- a/packages/core/src/credentials/claude.ts
+++ b/packages/core/src/credentials/claude.ts
@@ -373,6 +373,44 @@ export function buildBillingHeaderValue(
export const SYSTEM_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
+/**
+ * Build the request body for a Claude "wake" probe — a tiny, cheap message
+ * whose only purpose is to keep the subscription's rate-limit window warm.
+ *
+ * This MUST mirror the shape of a genuine Claude Code CLI request, because
+ * Anthropic validates the `system[]` array on OAuth (Pro/Max) -authenticated,
+ * Claude-Code-billed requests. A bare `{ model, messages }` body (no system
+ * identity) is rejected (401/403) — which is exactly how the old probe silently
+ * failed. The valid shape is:
+ *
+ * system: [
+ * { type: "text", text: "x-anthropic-billing-header: ..." }, // billing, no cache_control
+ * { type: "text", text: "You are Claude Code, Anthropic's official CLI for Claude." },
+ * ]
+ * messages: [ { role: "user", content: "hi" } ]
+ *
+ * Mirrors the runtime `transformClaudeOAuthBody` output for a single short user
+ * turn. Pure: deterministic given its inputs (the billing header samples only
+ * the user text), so it can be unit-tested without touching the network.
+ */
+export function buildWakeProbeBody(model: string): {
+ model: string;
+ max_tokens: number;
+ system: Array<{ type: "text"; text: string }>;
+ messages: Array<{ role: "user"; content: string }>;
+} {
+ const messages = [{ role: "user" as const, content: "hi" }];
+ return {
+ model,
+ max_tokens: 16,
+ system: [
+ { type: "text", text: buildBillingHeaderValue(messages) },
+ { type: "text", text: SYSTEM_IDENTITY },
+ ],
+ messages,
+ };
+}
+
// ─── Anthropic Request Headers ────────────────────────────────
export function getAnthropicHeaders(accessToken: string): Record<string, string> {
diff --git a/packages/core/src/credentials/index.ts b/packages/core/src/credentials/index.ts
index ff7392b..46fa5b6 100644
--- a/packages/core/src/credentials/index.ts
+++ b/packages/core/src/credentials/index.ts
@@ -9,6 +9,7 @@ export {
export {
ANTHROPIC_MODELS_FALLBACK,
buildBillingHeaderValue,
+ buildWakeProbeBody,
type ClaudeAccount,
type ClaudeCredentials,
type ClaudeProfile,