thedotmack/claude-mem · error

sync hub push: response missing acked array

Error message

sync hub push: response missing acked array

What it means

Thrown when the parsed push response JSON is missing the acked array (acked is not an Array). The hub must acknowledge every pushed op, so a missing/non-array acked is a contract violation before head_seq/projected_seq are even read.

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 d768ba3643)

Solutions

  1. Log the full parsed push response to see what shape the hub returned.
  2. Confirm hub and client run compatible protocol v2 versions.
  3. Verify the POST actually reached /v1/sync/ops and was not rerouted.
  4. If the hub omitted acked due to an internal error, treat the push as un-acked and re-push (the hub dedupes on (origin_device, kind, origin_id, rev)).
  5. Report a persistent missing-acked as a hub defect.

Example fix

// before: hub returns a status-style object without acked
// POST /v1/sync/ops -> {"head_seq":"42","projected_seq":"42"}
// after: hub returns the full ack contract
// POST /v1/sync/ops -> {"acked":[{...}],"head_seq":"42","projected_seq":"42"}
Defensive patterns

Strategy: validation

Type guard

function isPushMissingAcked(e: unknown): boolean {
  return e instanceof Error && /sync hub push: response missing acked array/i.test(e.message);
}

Try / catch

try { await cloudSync.pushOps(ops); }
catch (e) {
  if (isPushMissingAcked(e)) {
    // ops may have been applied; safe to re-push due to hub dedup
    await sleep(500); await cloudSync.pushOps(ops); return;
  }
  throw e;
}

Prevention

When it happens

Trigger: The hub returned JSON with acked absent, null, an object, or a non-array value; or the hub partially failed and omitted the ack list while still returning 200.

Common situations: Hub version mismatch where the response shape changed; hub accepted ops but the ack serialization path errored; a proxy returning a JSON status object instead of the push response; hub returning an error envelope that happens to be JSON but lacks acked.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/64fdbef91eeb2f3f. Report an issue: GitHub.