{"record":{"id":"64fdbef91eeb2f3f","repo":"thedotmack/claude-mem","slug":"sync-hub-push-response-missing-acked-array","errorCode":null,"errorMessage":"sync hub push: response missing acked array","messagePattern":"sync hub push: response missing acked array","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":979,"sourceCode":"    // error response is ambiguous (a degraded auth upstream 503s without\n    // the funnel) and must not read as \"switch cleared\".\n    const syncMode = res.headers.get('X-Sync-Mode');\n    if (syncMode !== null || res.ok) {\n      this.emitSyncMode(syncMode);\n    }\n    if (!res.ok) {\n      const body = (await res.text().catch(() => '')).slice(0, 200);\n      throw new Error(`sync hub push ${res.status}: ${body}`);\n    }\n    let parsed: unknown;\n    try {\n      parsed = await res.json();\n    } catch {\n      throw new Error('sync hub push: response is not JSON');\n    }\n    const acked = (parsed as { acked?: unknown } | null)?.acked;\n    if (!Array.isArray(acked)) {\n      throw new Error('sync hub push: response missing acked array');\n    }\n    const headSeq = (parsed as { head_seq?: unknown }).head_seq;\n    const projectedSeq = (parsed as { projected_seq?: unknown }).projected_seq;\n    if (typeof headSeq !== 'string' || typeof projectedSeq !== 'string') {\n      throw new Error('sync hub push: response requires decimal-string head_seq/projected_seq');\n    }\n    assertCanonicalDecimal(headSeq);\n    assertCanonicalDecimal(projectedSeq);\n    const validatedAcked = acked.map((value, index): AckedOp => {\n      if (!value || typeof value !== 'object' || Array.isArray(value)) {\n        throw new Error(`sync hub push: acked[${index}] must be an object`);\n      }\n      const item = value as Record<string, unknown>;\n      if (\n        typeof item.id !== 'string'\n        || typeof item.kind !== 'string'\n        || (item.origin_local_id !== null && typeof item.origin_local_id !== 'string')\n        || typeof item.entity_rev !== 'string'","sourceCodeStart":961,"sourceCodeEnd":997,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/services/sync/CloudSync.ts#L961-L997","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log the full parsed push response to see what shape the hub returned.","Confirm hub and client run compatible protocol v2 versions.","Verify the POST actually reached /v1/sync/ops and was not rerouted.","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)).","Report a persistent missing-acked as a hub defect."],"exampleFix":"// before: hub returns a status-style object without acked\n// POST /v1/sync/ops -> {\"head_seq\":\"42\",\"projected_seq\":\"42\"}\n// after: hub returns the full ack contract\n// POST /v1/sync/ops -> {\"acked\":[{...}],\"head_seq\":\"42\",\"projected_seq\":\"42\"}","handlingStrategy":"validation","validationCode":null,"typeGuard":"function isPushMissingAcked(e: unknown): boolean {\n  return e instanceof Error && /sync hub push: response missing acked array/i.test(e.message);\n}","tryCatchPattern":"try { await cloudSync.pushOps(ops); }\ncatch (e) {\n  if (isPushMissingAcked(e)) {\n    // ops may have been applied; safe to re-push due to hub dedup\n    await sleep(500); await cloudSync.pushOps(ops); return;\n  }\n  throw e;\n}","preventionTips":["Deploy hub versions that return the full { acked, head_seq, projected_seq } push contract.","Contract-test the push response for a non-empty acked array on success.","Rely on hub dedup so re-pushing un-acked ops is idempotent.","Log the parsed push response to catch shape regressions early."],"tags":["cloud-sync","push","hub","protocol","validation"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}