thedotmack/claude-mem · error · Error
status
Error message
status ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)} What it means
Thrown by the canary's `init` method when the `/v1/sync/status` health/bootstrap endpoint returns a non-2xx status, with the HTTP status and the first 200 bytes of the body included in the message. Because `init` is the first hub call of a canary run (called from `main`), this error typically means the run aborts immediately: the hub is unreachable, misconfigured, or refusing authentication.
Solutions
- Check the embedded status: 401 → update the device token secrets; 404 → fix the hub URL / redeploy the worker; 5xx → inspect hub logs and D1 health.
- Curl `/v1/sync/status` manually with the same headers to confirm whether the problem is hub-side or canary-side.
- Add a short retry with backoff for transient 5xx/429 before failing `main`.
- Confirm wrangler deploy of the sync-hub worker completed and the route matches `this.args.hub`.
Example fix
// before
if (!res.ok) {
throw new Error(`status ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}`);
}
// after
if (!res.ok) {
const body = await res.text().catch(() => "");
if (res.status >= 500 || res.status === 429) return this.retryInit();
throw new Error(`status ${res.status}: ${body.slice(0, 200)}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight check before running the canary
async function assertHubReady(hub: string, headers: HeadersInit) {
const res = await fetch(`${hub}/v1/sync/status`, { headers, signal: AbortSignal.timeout(5000) });
if (!res.ok) throw new Error(`hub not ready: ${res.status}; fix config before starting canary`);
} Type guard
function isStatusPayload(res: unknown): res is { head_seq: string; epoch: string } {
const r = res as any;
return r && typeof r.head_seq !== 'undefined' && typeof r.epoch !== 'undefined';
} Try / catch
try {
await canary.init();
} catch (err) {
if (err instanceof Error && /^status \d{3}:/.test(err.message)) {
const status = Number(err.message.slice(7, 10));
if (status === 401 || status === 403) console.error('check device token secrets');
if (status === 404) console.error('check hub URL / deployment');
process.exitCode = 1; // init failure aborts the whole run
} else throw err;
} Prevention
- Run a pre-flight health check on /v1/sync/status before scheduling canary runs.
- Keep canary device-token secrets in sync with hub token rotations.
- Verify post-deploy smoke tests cover the status endpoint before the canary cron fires.
- Pin the hub URL per environment and validate it at startup.
When it happens
Trigger: Any `fetch(`${hub}/v1/sync/status`)` response with `res.ok === false`: 401 for a missing/invalid DEVICE_A token, 404 when the hub route isn't deployed or the URL is wrong, 5xx when the hub worker or its database (D1) is down, or 429 rate limiting on a shared worker.
Common situations: Canary started before a hub deploy finished (route not yet live); hub URL env var points at staging vs production; device tokens rotated but canary secrets not updated; hub's D1 binding broken after a wrangler config change.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- pull
- [claude-mem] Worker GET
- [claude-mem] Worker POST
- Context requested before initialization complete, returning…
- Discord API error
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/19632b8749f49019.
Report an issue: GitHub.
Appendix: source
Thrown at workers/sync-hub/canary/canary.ts:278
if (!res.ok) {
throw new Error(`pull ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}`);
}
const parsed = (await res.json()) as {
ops: Array<{ seq: string; operation_sha256: string }>;
head_seq: string;
};
return { ops: parsed.ops, headSeq: parsed.head_seq };
}
/** Start both cursors at the current head — only NEW ops matter. */
async init(): Promise<void> {
const res = await fetch(`${this.args.hub}/v1/sync/status`, {
headers: this.headers(DEVICE_A),
signal: AbortSignal.timeout(this.args.timeoutMs),
});
this.noteMode(res);
if (!res.ok) {
throw new Error(`status ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}`);
}
const status = (await res.json()) as { head_seq: string; epoch: string };
this.cursors[DEVICE_A] = String(status.head_seq);
this.cursors[DEVICE_B] = String(status.head_seq);
log({
event: "init",
hub: this.args.hub,
user: this.args.user,
head_seq: status.head_seq,
epoch: status.epoch,
sync_mode: this.syncMode,
});
}
/**
* One trickle cycle: origin pushes a tiny op; the OTHER device polls its
* cursor forward until it sees that exact op (or the bound expires).
*/View on GitHub (pinned to d8bc9755e7)