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

  1. 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.
  2. Curl `/v1/sync/status` manually with the same headers to confirm whether the problem is hub-side or canary-side.
  3. Add a short retry with backoff for transient 5xx/429 before failing `main`.
  4. 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

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


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)