thedotmack/claude-mem · error · Error

push

Error message

push ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}

What it means

The sync-hub canary's push method POSTs an operation to the hub and expects an OK response containing an ack for the operation's SHA-256. If the HTTP response status is not ok, it throws with the status code and the first 200 characters of the response body (body read failures degrade to an empty string). This surfaces server-side rejection details for debugging the sync protocol.

Solutions

  1. Inspect the response body snippet in the error to identify the hub's rejection reason
  2. Handle 409 conflicts idempotently — treat already-acked operations as success
  3. Retry with backoff on 5xx/503 responses
  4. Check hub auth/credentials if the status is 401/403

Example fix

// before
await canary.push(body, operationSha256); // throws on !res.ok
// after
try {
  await canary.push(body, operationSha256);
} catch (e) {
  if (String(e).startsWith('push 409')) return; // already acked
  await backoffRetry(() => canary.push(body, operationSha256));
}
Defensive patterns

Strategy: retry

Validate before calling

const body = JSON.stringify(ops);
if (body.length > MAX_PUSH_BYTES) throw new Error('operation too large for single push');

Try / catch

try { await client.push(body, sha); }
catch (e) {
  if (/push 409/.test(String(e))) return; // duplicate op, treat as acked
  if (/push 5\d\d/.test(String(e))) { await backoffRetry(() => client.push(body, sha)); return; }
  throw e;
}

Prevention

When it happens

Trigger: The hub returns a non-2xx status to the push request — e.g. 409 conflict on a duplicate operation_sha256, 413 payload too large, 500 server error, 503 during maintenance.

Common situations: Replaying an operation the hub already acked; hub deployment/maintenance window; oversized operation bodies; auth token expiry on the canary client.

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/01d49568147a7b72. Report an issue: GitHub.

Appendix: source

Thrown at workers/sync-hub/canary/canary.ts:240

			origin_local_id: localId,
			payload,
			payload_schema_version: 2,
			payload_sha256: await sha256Base64Url(canonicalJson(payload)),
		};
		const body = canonicalJson(envelope);
		const operationSha256 = await sha256Base64Url(body);
		const res = await fetch(`${this.args.hub}/v1/sync/ops`, {
			method: "POST",
			headers: { ...this.headers(deviceId), "Content-Type": "application/json" },
			body: JSON.stringify({
				protocol_version: 2,
				ops: [{ body, operation_sha256: operationSha256 }],
			}),
			signal: AbortSignal.timeout(this.args.timeoutMs),
		});
		this.noteMode(res);
		if (!res.ok) {
			throw new Error(`push ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}`);
		}
		const parsed = (await res.json()) as {
			acked: Array<{ operation_sha256: string; seq: string }>;
			head_seq: string;
		};
		const ack = parsed.acked.find((a) => a.operation_sha256 === operationSha256);
		if (!ack) throw new Error("push response did not ack the canary op");
		return { seq: ack.seq, headSeq: parsed.head_seq, opSha: operationSha256 };
	}

	private async pull(
		deviceId: string,
		since: string,
	): Promise<{ ops: Array<{ seq: string; operation_sha256: string }>; headSeq: string }> {
		const res = await fetch(`${this.args.hub}/v1/sync/changes?since=${since}&limit=500`, {
			headers: this.headers(deviceId),
			signal: AbortSignal.timeout(this.args.timeoutMs),
		});

View on GitHub (pinned to d8bc9755e7)