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

  1. 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
  2. Re-authenticate with the header of the device that actually produced the ops if pushing another device's local queue
  3. Inspect and correct device provisioning so ops are stamped with their true originating device id
  4. 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


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)