thedotmack/claude-mem · error · Error
origin_device_id does not match authenticated X-Device-Id
Error message
origin_device_id does not match authenticated X-Device-Id
What it means
In SyncHubDO.pushOps, each incoming op body is checked against the deviceId authenticated via the X-Device-Id header. This error fires when an op's payload origin_device_id differs from that authenticated device — i.e. a client is attempting to append operations that claim to originate from another device. It is an authorization guard against cross-device spoofing; the throw is caught by the per-op wrapper and rethrown with the invalid(...)/INVALID_OPS_PREFIX sentinel, causing the whole push to be refused.
Solutions
- Ensure the client sets each op body's origin_device_id to the same device id it authenticates with in the X-Device-Id header
- Re-authenticate with the header of the device that actually produced the ops if pushing another device's local queue
- Inspect and correct device provisioning so ops are stamped with their true originating device id
- Drop or re-originate stale ops that were authored on a device whose id has changed
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at workers/sync-hub/src/do/SyncHub.ts:382 when the library encounters an invalid state.
Common situations: See trigger scenarios.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/59415446773364d9.
Report an issue: GitHub.
Appendix: source
Thrown at workers/sync-hub/src/do/SyncHub.ts:382
} catch (error) {
console.error("sync-hub fan-out failed (advisory; push unaffected):", error);
}
}
// ---------------------------------------------------------------------
// Canonical append path and client cursor reads.
// ---------------------------------------------------------------------
async pushOps(deviceId: string, ops: PushOp[], deviceName: string | null = null): Promise<PushOutcome> {
let rows: ValidatedOp[];
try {
if (typeof deviceId !== "string" || deviceId.length === 0) throw invalid("deviceId must be non-empty");
if (!Array.isArray(ops)) throw invalid("ops must be an array");
rows = await Promise.all(ops.map(async (op, index) => {
try {
const parsed = await parseCanonicalOperation(op);
if (parsed.body.origin_device_id !== deviceId) {
throw new Error("origin_device_id does not match authenticated X-Device-Id");
}
return parsed;
} catch (error) {
throw invalid(`ops[${index}] ${error instanceof Error ? error.message : String(error)}`);
}
}));
} catch (error) {
if (error instanceof Error && error.message.startsWith(INVALID_OPS_PREFIX)) {
return { refused: true, error: error.message };
}
if (isDeviceLimitError(error)) return { refused: true, error: DEVICE_LIMIT_ERROR };
throw error;
}
const sql = this.ctx.storage.sql;
const now = Date.now();
const nowDecimal = String(now);
const headBefore = this.headSeq();View on GitHub (pinned to d8bc9755e7)