thedotmack/claude-mem · error
sync hub push: malformed acked
Error message
sync hub push: malformed acked[${index}] What it means
An acked[] entry has a missing or wrong-typed required field (id, kind, origin_local_id, entity_rev, operation_sha256, seq), or one of its decimal strings is non-canonical — assertCanonicalDecimal rejects leading zeros, signs, values above uint64, and 0 for entity_rev/seq (positive required). Validation precedes stamping; flush() backs off and retries unchanged.
Solutions
- Dump the offending ack entry (wrap sendOps in a logger or curl a minimal push) and compare each field against the contract
- Fix field emission: origin_local_id must be null-or-decimal-string, entity_rev/seq positive canonical decimals without padding
- Reject sequences above uint64 at the hub's append path (that is also a client-side uint64 overflow signal)
- Add a hub-side schema test that validates its own responses with the same canonical-decimal rules
Example fix
// hub — before: zero-padded log formatting leaks to the wire
acked.push({ id, kind, origin_local_id, entity_rev, operation_sha256, seq: seq.toString().padStart(6, '0') });
// after: canonical decimal, no padding
acked.push({ id, kind, origin_local_id, entity_rev, operation_sha256, seq: String(seq) }); Defensive patterns
Strategy: type-guard
Validate before calling
// Full ack-entry validation mirror (hub contract tests):
const CANON = /^(?:0|[1-9][0-9]{0,19})$/;
function isValidAckedOp(a: unknown): boolean {
if (!a || typeof a !== 'object' || Array.isArray(a)) return false;
const i = a as Record<string, unknown>;
return typeof i.id === 'string' && typeof i.kind === 'string'
&& (i.origin_local_id === null || (typeof i.origin_local_id === 'string' && CANON.test(i.origin_local_id)))
&& typeof i.entity_rev === 'string' && CANON.test(i.entity_rev) && i.entity_rev !== '0'
&& typeof i.operation_sha256 === 'string'
&& typeof i.seq === 'string' && CANON.test(i.seq) && i.seq !== '0';
} Type guard
function isCanonicalDecimal(v: unknown, positive = false): v is string {
return typeof v === 'string' && /^(?:0|[1-9][0-9]{0,19})$/.test(v) && (!positive || v !== '0');
} Try / catch
const m = (sync.status().lastError ?? '').match(/malformed acked\[(\d+)\]/);
if (m) {
// index tells you which entry to inspect in the captured hub response; check field types and decimal form
} Prevention
- Never pad or sign sequence/rev strings on the hub; emit String(n) directly
- Reject seqs above uint64 at the hub append path
- Reuse one shared canonical-decimal validator on hub and client so rules cannot drift
When it happens
Trigger: Hub omits origin_local_id for mutation acks (must be null, not undefined/missing after serialization quirks); seq serialized as number then coerced; entity_rev='0' emitted for a first revision; hub emits zero-padded sequences like '0042'; a 20+ digit seq exceeding uint64.
Common situations: Hub ported to another language with different number-formatting defaults; format-string padding introduced for logs leaking into the wire body; version skew; hand-maintained hub fixtures.
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- sync hub push: acked
- sync hub push: 200 response acknowledgment multiplicity…
- sync hub push: 200 response contains an extra or mismatched…
- sync hub push: acknowledgment seq exceeds head_seq
- sync hub push: duplicate operation tuple claimed different…
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/44f9374f94e4e9ab.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/CloudSync.ts:1001
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 {
acked: validatedAcked,
head_seq: headSeq,
projected_seq: projectedSeq,
};
}
/**
* Treat a successful push response as one atomic acknowledgment proof.
* Nothing in this method mutates SQLite. stampAcked() runs only after every
* tuple, multiplicity, sequence, and checkpoint invariant has passed.
*/View on GitHub (pinned to e2d1df569a)