{"record":{"id":"b15aafcd889caed6","repo":"thedotmack/claude-mem","slug":"sync-hub-status-response-requires-decimal-string","errorCode":null,"errorMessage":"sync hub status: response requires decimal-string epoch/head_seq/projected_seq","messagePattern":"sync hub status: response requires decimal-string epoch/head_seq/projected_seq","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":677,"sourceCode":"      let parsed: unknown;\n      try {\n        parsed = await response.json();\n      } catch {\n        throw new Error('sync hub status: response is not JSON');\n      }\n      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n        throw new Error('sync hub status: response must be an object');\n      }\n      const record = parsed as Record<string, unknown>;\n      if (record.protocol_version !== 2) {\n        throw new Error('sync hub status: response requires protocol_version 2');\n      }\n      if (\n        typeof record.epoch !== 'string'\n        || typeof record.head_seq !== 'string'\n        || typeof record.projected_seq !== 'string'\n      ) {\n        throw new Error('sync hub status: response requires decimal-string epoch/head_seq/projected_seq');\n      }\n      const epoch = assertCanonicalDecimal(record.epoch, { positive: true });\n      const headSeq = assertCanonicalDecimal(record.head_seq);\n      const projectedSeq = assertCanonicalDecimal(record.projected_seq);\n      if (compareCanonicalDecimals(projectedSeq, headSeq) > 0) {\n        throw new Error('sync hub status: projected_seq exceeds head_seq');\n      }\n      this.hubStatus = {\n        checkedAt,\n        reachable: true,\n        epoch,\n        headSeq,\n        projectedSeq,\n        error: null,\n      };\n    } catch (error) {\n      const raw = error instanceof Error ? error.message : String(error);\n      const safe = this.token === '' ? raw : raw.split(this.token).join('[REDACTED]');","sourceCodeStart":659,"sourceCodeEnd":695,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/CloudSync.ts#L659-L695","documentation":"The protocol requires epoch, head_seq and projected_seq to be strings containing canonical decimal integers (arbitrary precision). probeHubStatus throws 'sync hub status: response requires decimal-string epoch/head_seq/projected_seq' when any field is missing, not a string, or not a canonical decimal, before further arithmetic via assertCanonicalDecimal/compareCanonicalDecimals.","triggerScenarios":"Hub returns these fields as numbers instead of strings, omits one of them, uses non-canonical forms (leading '+', leading zeros, whitespace, empty string), or sends null.","commonSituations":"A hub implementation serializing seq counters as JSON numbers, a hand-rolled hub using different field names or formats, JSON fixtures written by hand with numeric epochs.","solutions":["Fix the hub to serialize epoch/head_seq/projected_seq as decimal strings, e.g. String(seq).","Reject/handle numeric sequences at the client edge by normalizing before the probe if you control both sides.","Validate payloads in hub tests with assertCanonicalDecimal to catch non-canonical forms early.","Confirm field names are exactly epoch, head_seq, projected_seq (lowercase, snake_case)."],"exampleFix":"// before (hub)\nres.json({ protocol_version: 2, epoch: 1726000000, head_seq: 42, projected_seq: 42 });\n// after\nres.json({ protocol_version: 2, epoch: '1726000000', head_seq: '42', projected_seq: '42' });","handlingStrategy":"validation","validationCode":"function isCanonicalDecimal(v: unknown): v is string {\n  return typeof v === 'string' && /^\\d+$/.test(v) && (v === '0' || !v.startsWith('0'));\n}\nif (!isCanonicalDecimal(body.epoch) || !isCanonicalDecimal(body.head_seq) || !isCanonicalDecimal(body.projected_seq)) {\n  throw new Error('hub seq fields must be canonical decimal strings');\n}","typeGuard":"function isDecimalStringSeq(v: unknown): v is string {\n  return typeof v === 'string' && /^\\d+$/.test(v);\n}","tryCatchPattern":"try {\n  const status = await cloudSync.statusWithHubProbe();\n} catch (e) {\n  if (e.message.includes('decimal-string epoch/head_seq/projected_seq')) {\n    logger.error('hub serializes seq counters in wrong format (numbers/non-canonical strings)', { raw: lastRawBody });\n  } else throw e;\n}","preventionTips":["Always serialize sequence counters as JSON strings, never numbers","Reject numeric or non-canonical seq values in hub tests with assertCanonicalDecimal","Keep field names exactly epoch/head_seq/projected_seq in hub implementations","Add a schema test asserting the full status payload shape"],"tags":["schema","validation","sync-hub","decimal-string"],"backgroundTag":"invalid-argument-format","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}