{"record":{"id":"70acff8c9a3019aa","repo":"thedotmack/claude-mem","slug":"sync-hub-status-response-is-not-json","errorCode":null,"errorMessage":"sync hub status: response is not JSON","messagePattern":"sync hub status: response is not JSON","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":663,"sourceCode":"          'X-User-Id': this.userId,\n          'X-Device-Id': this.deviceId,\n          ...(this.deviceName ? { 'X-Device-Name': this.deviceName } : {}),\n        },\n        signal: AbortSignal.timeout(this.requestTimeoutMs),\n      });\n      checkedAt = Date.now();\n      const syncMode = response.headers.get('X-Sync-Mode');\n      if (syncMode !== null || response.ok) this.emitSyncMode(syncMode);\n      if (!response.ok) {\n        const body = (await response.text().catch(() => '')).slice(0, 200);\n        throw new Error(`sync hub status ${response.status}: ${body}`);\n      }\n\n      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);","sourceCodeStart":645,"sourceCodeEnd":681,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/CloudSync.ts#L645-L681","documentation":"probeHubStatus expects the hub status endpoint to return a JSON body. If response.json() throws — e.g. an HTML error page, empty body, or non-JSON content — it throws 'sync hub status: response is not JSON'. This guards the protocol client against talking to a non-conforming endpoint.","triggerScenarios":"Hub returns text/html (proxy error page, login redirect), an empty body, or wrong Content-Type so response.json() fails to parse.","commonSituations":"Wrong URL pointing at a web UI instead of the API, auth middleware redirecting to a login page, proxy/captive-portal injecting HTML, hub returning a 200 with empty body.","solutions":["Verify the hub status URL points to the JSON status API endpoint, not a UI or proxy page.","Check for redirects/auth walls: log response.url and status before parsing.","Ensure the hub serves Content-Type application/json and a valid JSON body.","Check network middleboxes (corporate proxies, VPNs) that may inject HTML error pages.","If an HTML page is returned, surface the first bytes in logs to identify what actually answered."],"exampleFix":"// before: blind probe\nconst res = await fetch(hubUrl + '/status');\n// after: verify endpoint returns JSON before probing\nconst res = await fetch(hubUrl + '/status', { redirect: 'follow' });\nif ((res.headers.get('content-type') || '').includes('application/json')) {\n  const status = await cloudSync.statusWithHubProbe();\n} else {\n  logger.warn('hub status endpoint did not return JSON', { url: res.url });\n}","handlingStrategy":"validation","validationCode":"const res = await fetch(hubUrl + '/status');\nconst ct = res.headers.get('content-type') || '';\nif (!ct.includes('application/json')) {\n  throw new Error(`hub status returned non-JSON (${ct}): ${(await res.text()).slice(0, 100)}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const status = await cloudSync.statusWithHubProbe();\n} catch (e) {\n  if (e.message === 'sync hub status: response is not JSON') {\n    logger.error('hub status endpoint not JSON — check URL/redirects/proxies', { url: hubUrl });\n    fallBackToLocalStatus();\n  } else throw e;\n}","preventionTips":["Point the client at the JSON status API, not a UI or login page","Check for redirects: HTML usually means an auth wall or proxy error page","Verify the hub returns Content-Type application/json","Inspect the raw body in logs when parsing fails to see what actually answered"],"tags":["json","http","sync-hub","parsing"],"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"}