{"record":{"id":"71340f66efa54e49","repo":"thedotmack/claude-mem","slug":"sync-hub-push-response-is-not-json","errorCode":null,"errorMessage":"sync hub push: response is not JSON","messagePattern":"sync hub push: response is not JSON","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":1202,"sourceCode":"    // paths would keep hammering the socket through an incident.\n    // Asymmetric on purpose (SyncClient.onSyncModeHint contract): header\n    // PRESENCE is emitted regardless of status; header ABSENCE is only\n    // emitted (as null = \"cleared\") from an OK response — absence on an\n    // 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 (","sourceCodeStart":1184,"sourceCodeEnd":1220,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/CloudSync.ts#L1184-L1220","documentation":"After a successful (2xx) push, pushOps parses the response body as JSON. If the body is not valid JSON — typically because an intermediary returned an HTML page or empty body with a 200 status — the client throws this error instead of proceeding to validate the acked array.","triggerScenarios":"Hub or a proxy/gateway returns 200 with a non-JSON body (HTML login page, empty body, CORS/error page) for POST /v1/sync/ops.","commonSituations":"Misconfigured reverse proxy intercepting requests; hub mis-deployed behind an auth wall; wrong hubUrl pointing at a non-sync server; network appliance rewriting responses.","solutions":["Verify hubUrl points at the actual sync hub endpoint, not a proxy or UI host.","Check proxy/gateway config for pages that intercept 200 responses.","Inspect raw response with curl to see what the hub actually returns.","Redeploy or upgrade the hub so /v1/sync/ops returns a proper JSON PushResponse."],"exampleFix":"// before\nhubUrl: process.env.CLAUDE_MEM_CLOUD_SYNC_HUB_URL\n// after\n// ensure it is the API base, e.g.\nhubUrl: 'https://sync.example.com' // serves JSON at /v1/sync/ops","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isPushResponse(v: unknown): v is { acked: unknown[]; head_seq?: number } {\n  return typeof v === 'object' && v !== null && Array.isArray((v as any).acked);\n}","tryCatchPattern":"try { await client.pushOps(ops); } catch (e) { if (e.message === 'sync hub push: response is not JSON') { /* check hubUrl / proxy config */ } }","preventionTips":["Point hubUrl directly at the sync API host, not a UI or proxy.","curl the endpoint once during setup to confirm JSON responses.","Check for captive-portal or auth-wall proxies in corporate networks."],"tags":["sync","http","json"],"backgroundTag":"invalid-json-response","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"}