thedotmack/claude-mem · error
sync hub push: acked[${index}] must be an object
Error message
sync hub push: acked[${index}] must be an object What it means
Thrown while parsing the JSON body of a successful push to the sync hub. Each element of the `acked` array must be a plain object; this fires when an element is null, a primitive (string/number/boolean), or an Array. It is the first of two shape checks on acked entries (the second, [121], checks field types), so hitting it means the hub returned a structurally wrong ack list, not merely a mistyped field.
Source
Thrown at src/services/sync/CloudSync.ts:990
try {
parsed = await res.json();
} catch {
throw new Error('sync hub push: response is not JSON');
}
const acked = (parsed as { acked?: unknown } | null)?.acked;
if (!Array.isArray(acked)) {
throw new Error('sync hub push: response missing acked array');
}
const headSeq = (parsed as { head_seq?: unknown }).head_seq;
const projectedSeq = (parsed as { projected_seq?: unknown }).projected_seq;
if (typeof headSeq !== 'string' || typeof projectedSeq !== 'string') {
throw new Error('sync hub push: response requires decimal-string head_seq/projected_seq');
}
assertCanonicalDecimal(headSeq);
assertCanonicalDecimal(projectedSeq);
const validatedAcked = acked.map((value, index): AckedOp => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`sync hub push: acked[${index}] must be an object`);
}
const item = value as Record<string, unknown>;
if (
typeof item.id !== 'string'
|| typeof item.kind !== 'string'
|| (item.origin_local_id !== null && typeof item.origin_local_id !== 'string')
|| typeof item.entity_rev !== 'string'
|| typeof item.operation_sha256 !== 'string'
|| typeof item.seq !== 'string'
) {
throw new Error(`sync hub push: malformed acked[${index}]`);
}
assertCanonicalDecimal(item.entity_rev, { positive: true });
assertCanonicalDecimal(item.seq, { positive: true });
if (typeof item.origin_local_id === 'string') assertCanonicalDecimal(item.origin_local_id);
return item as unknown as AckedOp;
});
return {View on GitHub (pinned to d768ba3643)
Solutions
- Inspect the raw hub response body (add a temporary log of `parsed` right before the `acked.map` call) to see the actual non-object element.
- Confirm the sync hub software version matches the client CloudSync.ts version; a hub that serializes ack tuples as arrays or strings instead of objects indicates version skew.
- If you control the hub, ensure every `acked` entry is a JSON object with the AckedOp shape (id, kind, origin_local_id, entity_rev, operation_sha256, seq).
- Upgrade the client and hub together so the wire contract agrees on object-shaped ack entries.
Example fix
// before (hub returning string acks): { "acked": ["seq-1", "seq-2"], ... }
// after (object acks): { "acked": [ { "id": "...", "kind": "observation", "origin_local_id": null, "entity_rev": "1", "operation_sha256": "...", "seq": "1" } ], ... } Defensive patterns
Strategy: try-catch
Validate before calling
// Before calling cloudSync.push(pushed), you cannot pre-validate the hub body
// (it is produced server-side). Validate your *input* instead:
function assertWireOps(pushed: WireOp[]): void {
for (const op of pushed) {
if (typeof op.operation_sha256 !== 'string') throw new Error('push: operation_sha256 must be string');
parseCanonicalOperation(op); // throws on bad body
}
} Type guard
function isPlainObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Try / catch
try {
const resp = await cloudSync.push(pushed);
} catch (e) {
if (e instanceof Error && e.message.startsWith('sync hub push:')) {
logger.error('SYNC', 'hub response shape rejected', { msg: e.message }, e);
// do not stamp outbox; leave ops for next push cycle
return;
}
throw e;
} Prevention
- Pin hub and client versions together so the acked wire schema matches.
- Log the raw parsed response once when integrating a new hub to confirm object-shaped acks.
- Never hand-craft push-response mocks with non-object ack entries; mirror the AckedOp shape.
When it happens
Trigger: A POST to the sync hub push endpoint returns HTTP 200 with an `acked` array containing a non-object element (e.g. `acked: ["123", null, []]` or a string-encoded entry). Reachable only inside the push response parser after `acked` is confirmed to be an array and `head_seq`/`projected_seq` are validated decimal strings.
Common situations: Hub version skew (server serializes acks as bare strings or wraps them differently than the client expects), a misbehaving proxy/CDN that rewrites the JSON body, or a test stub returning `acked: ["seq-1"]`. Also seen when the hub begins returning a new envelope shape before the client is upgraded.
Related errors
- sync hub push: malformed acked[${index}]
- sync hub push: 200 response contains an extra or mismatched
- sync hub push: duplicate operation tuple claimed different s
- sync hub push: distinct operation tuples claimed the same se
- sync hub push: 200 response acknowledgment multiplicity mism
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/98ddc9679fc72f0c.
Report an issue: GitHub.