thedotmack/claude-mem · error · Error

ops[ ]

Error message

ops[${index}] ${error instanceof Error ? error.message : String(error)}

What it means

Aggregated per-op validation failure in SyncHubDO.pushOps: while parsing/validating ops[index], parseCanonicalOperation or the origin_device_id match check failed, and the inner message is prefixed onto ops[index] to pinpoint the offending entry. The whole batch is refused with this message via INVALID_OPS_PREFIX.

Solutions

  1. Read the index in ops[N] and the inner message to find the bad op
  2. Ensure every op's origin_device_id equals the authenticated X-Device-Id
  3. Drop or re-create the offending op client-side and re-push the rest
  4. Upgrade/downgrade the client so its op schema matches the hub's parser
Defensive patterns

Strategy: validation

Validate before calling

ops.forEach((op, i) => {
  if (op.body?.origin_device_id !== deviceId) throw new Error(`op ${i} origin_device_id mismatch`);
});

Try / catch

try {
  return await hub.push(deviceId, ops);
} catch (e) {
  const m = String(e).match(/ops\[(\d+)\] (.+)/);
  if (m) { dropOp(Number(m[1])); return retryPushWithoutBadOps(); }
  throw e;
}

Prevention

When it happens

Trigger: Any op in the batch fails schema parsing (bad kind, malformed body, invalid revision format) or its parsed.body.origin_device_id differs from the authenticated deviceId — commonly op #N where a batch mixes devices or contains a hand-crafted op.

Common situations: Merged/replayed batches after a client restore containing ops from an old device id, schema drift after a client upgrade introducing a new op kind the hub doesn't parse, corrupted local op queue.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/29fd0ba4731b7b22. Report an issue: GitHub.

Appendix: source

Thrown at workers/sync-hub/src/do/SyncHub.ts:386

	// ---------------------------------------------------------------------
	// 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();
		const acked: AckedOp[] = [];
		try {
			this.ctx.storage.transactionSync(() => {
				this.touchDevice(deviceId, normalizeDeviceName(deviceName), now);

View on GitHub (pinned to d8bc9755e7)