{"record":{"id":"427d4bc59b34e011","repo":"thedotmack/claude-mem","slug":"sync-hub-push-response-requires-decimal-string-he","errorCode":null,"errorMessage":"sync hub push: response requires decimal-string head_seq/projected_seq","messagePattern":"sync hub push: response requires decimal-string head_seq/projected_seq","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":984,"sourceCode":"    }\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'\n        || typeof item.operation_sha256 !== 'string'\n        || typeof item.seq !== 'string'\n      ) {\n        throw new Error(`sync hub push: malformed acked[${index}]`);\n      }","sourceCodeStart":966,"sourceCodeEnd":1002,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/sync/CloudSync.ts#L966-L1002","documentation":"The push response's head_seq or projected_seq is not a JSON string (a number, null, or missing). Protocol v2 transmits uint64 sequences as decimal strings because JSON numbers lose precision past 2^53; this is a typeof check only, with assertCanonicalDecimal verifying canonical form immediately after. Nothing is stamped locally; flush() retries via backoff.","triggerScenarios":"Hub serializes the sequences as numbers; one field renamed or dropped in a hub build; an intermediate transform re-encodes them; stub responses in tests using integers.","commonSituations":"Hub/client version skew after upgrading only one side; a hub port from another language defaulting to integer JSON; contract drift introduced during hub refactoring.","solutions":["Inspect the raw push response (curl with an empty ops array) and confirm both fields are quoted decimal strings","Fix the hub serializer: String(headSeq), String(projectedSeq) before JSON encoding","Align hub and client versions so both speak protocol v2","Add a contract test on the hub asserting typeof body.head_seq === 'string'"],"exampleFix":"// hub push handler — before\nreturn Response.json({ acked, head_seq: head, projected_seq: projected });\n\n// after\nreturn Response.json({ acked, head_seq: String(head), projected_seq: String(projected) });","handlingStrategy":"type-guard","validationCode":"// Hub-side self-check before responding:\nconst payload = { acked, head_seq: String(headSeq), projected_seq: String(projectedSeq) };\nif (typeof payload.head_seq !== 'string' || typeof payload.projected_seq !== 'string') throw new Error('bad envelope');","typeGuard":"function isDecimalStringPair(v: { head_seq?: unknown; projected_seq?: unknown }): v is { head_seq: string; projected_seq: string } {\n  return typeof v.head_seq === 'string' && typeof v.projected_seq === 'string';\n}","tryCatchPattern":"if (/decimal-string head_seq\\/projected_seq/.test(sync.status().lastError ?? '')) {\n  // typeof violation, not formatting: the hub emitted numbers/null — fix the hub serializer\n}","preventionTips":["Serialize uint64 domains with String() on the hub; never let integer JSON defaults reach the wire","Contract-test both directions (status + push responses) for string-typed sequences","Beware ports of the hub to other languages — JSON number defaults differ"],"tags":["sync","hub","protocol-validation","type-mismatch","sequence"],"backgroundTag":"schema-validation-failed","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}