thedotmack/claude-mem · error
sync hub push: response missing acked array
Error message
sync hub push: response missing acked array
What it means
The push response parsed as JSON but its acked field is not an array (missing, null, or an object). Protocol v2 requires the response to carry the complete acked array plus head_seq/projected_seq; validation runs before stampAcked so nothing is written locally, and flush() backs off and retries the same ops safely. This indicates a hub that does not implement the v2 response envelope — usually version skew.
Solutions
- curl -X POST the ops endpoint with an empty ops array and inspect the exact response shape
- Deploy a hub build that returns { acked: [...], head_seq: "...", projected_seq: "..." } for protocol v2
- Verify the load balancer routes /v1/sync/* to the sync hub, not another backend
- Pin client and hub versions together in deployment so the wire contract stays matched
Defensive patterns
Strategy: type-guard
Validate before calling
// Validate a push response envelope in hub contract tests:
function validPushEnvelope(v: unknown): boolean {
if (!v || typeof v !== 'object') return false;
const r = v as Record<string, unknown>;
return Array.isArray(r.acked)
&& typeof r.head_seq === 'string'
&& typeof r.projected_seq === 'string';
} Type guard
function isPushResponseShape(v: unknown): v is { acked: unknown[]; head_seq: string; projected_seq: string } {
return !!v && typeof v === 'object' && !Array.isArray(v)
&& Array.isArray((v as Record<string, unknown>).acked)
&& typeof (v as Record<string, unknown>).head_seq === 'string'
&& typeof (v as Record<string, unknown>).projected_seq === 'string';
} Try / catch
// Internal; surfaces via lastError after backoff:
if (/missing acked array/.test(sync.status().lastError ?? '')) {
// hub is not speaking the v2 response contract: check hub version/deployment before retrying
} Prevention
- Run hub response-envelope contract tests in CI against the deployed client build
- Pin hub and client releases together; never partial-upgrade one side
- Make load balancer health checks hit a hub-specific route so /v1/sync/ops never lands elsewhere
When it happens
Trigger: Hub build predating protocol v2 returning a different response shape (e.g. {ok:true}); a field rename on the hub; a proxy returning its own JSON (health-check style) with 200; a partially deployed hub.
Common situations: Client upgraded ahead of the hub; hub rolled back mid-incident; load balancer routing /v1/sync/ops to the wrong service; test stub answering with a placeholder body.
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
- sync hub push: acked
- sync hub push: malformed acked
- sync hub push: response requires decimal-string…
- sync hub pull: malformed /changes response
- sync hub pull: more must be boolean
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/64fdbef91eeb2f3f.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/CloudSync.ts:979
// error response is ambiguous (a degraded auth upstream 503s without
// the funnel) and must not read as "switch cleared".
const syncMode = res.headers.get('X-Sync-Mode');
if (syncMode !== null || res.ok) {
this.emitSyncMode(syncMode);
}
if (!res.ok) {
const body = (await res.text().catch(() => '')).slice(0, 200);
throw new Error(`sync hub push ${res.status}: ${body}`);
}
let parsed: unknown;
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'View on GitHub (pinned to e2d1df569a)