thedotmack/claude-mem · error
sync hub status: response is not JSON
Error message
sync hub status: response is not JSON
What it means
Thrown by probeHubStatus when response.ok but response.json() threw — the hub returned 200 with a body that is not valid JSON (or the stream errored). This is a protocol/contract violation: a 200 on /v1/sync/status must be a JSON object.
Source
Thrown at src/services/sync/CloudSync.ts:603
'X-User-Id': this.userId,
'X-Device-Id': this.deviceId,
...(this.deviceName ? { 'X-Device-Name': this.deviceName } : {}),
},
signal: AbortSignal.timeout(this.requestTimeoutMs),
});
checkedAt = Date.now();
const syncMode = response.headers.get('X-Sync-Mode');
if (syncMode !== null || response.ok) this.emitSyncMode(syncMode);
if (!response.ok) {
const body = (await response.text().catch(() => '')).slice(0, 200);
throw new Error(`sync hub status ${response.status}: ${body}`);
}
let parsed: unknown;
try {
parsed = await response.json();
} catch {
throw new Error('sync hub status: response is not JSON');
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('sync hub status: response must be an object');
}
const record = parsed as Record<string, unknown>;
if (record.protocol_version !== 2) {
throw new Error('sync hub status: response requires protocol_version 2');
}
if (
typeof record.epoch !== 'string'
|| typeof record.head_seq !== 'string'
|| typeof record.projected_seq !== 'string'
) {
throw new Error('sync hub status: response requires decimal-string epoch/head_seq/projected_seq');
}
const epoch = assertCanonicalDecimal(record.epoch, { positive: true });
const headSeq = assertCanonicalDecimal(record.head_seq);
const projectedSeq = assertCanonicalDecimal(record.projected_seq);View on GitHub (pinned to d768ba3643)
Solutions
- Capture the raw response body and Content-Type header to see what the hub actually returned.
- Verify hubUrl points at the real sync hub and not a generic web server / captive portal.
- Check for an intercepting proxy/gateway returning HTML; bypass it or fix its routing.
- If the hub itself is serving non-JSON on 200, that is a hub bug — report it to the hub operator.
- Ensure no middleware rewrites 5xx into 200-with-HTML.
Example fix
// before: hubUrl hits a generic web server hubUrl = 'https://example.com'; // after: hubUrl hits the actual sync hub API base hubUrl = 'https://sync.example.com'; // serves /v1/sync/status as JSON
Defensive patterns
Strategy: validation
Type guard
function isHubStatusNotJson(e: unknown): boolean {
return e instanceof Error && /sync hub status: response is not JSON/i.test(e.message);
} Try / catch
try { await cloudSync.status(); }
catch (e) { if (isHubStatusNotJson(e)) { markHubMisconfigured(e.message); return; } throw e; } Prevention
- Confirm hubUrl serves the JSON sync API, not an HTML site or captive portal.
- Bypass or fix reverse proxies/CDNs that return HTML on 200.
- Validate the response Content-Type is application/json in integration tests against the hub.
- Capture the raw body once to diagnose which middlebox is rewriting responses.
When it happens
Trigger: The hub returned 200 but the body was HTML (error page from a misconfigured proxy/CDN), empty, truncated, or text; a transparent proxy returned a captive-portal page; the response Content-Type was not application/json and the body was not parseable.
Common situations: A reverse proxy/CDN intercepting the request and returning an HTML error page with status 200; hub misconfigured to serve a status page instead of JSON; network middlebox truncating the body; pointing hubUrl at a non-hub host.
Related errors
- sync hub status: response must be an object
- sync hub push: response is not JSON
- sync hub status ${response.status}: ${body}
- sync hub status: response requires decimal-string epoch/head
- sync hub push ${res.status}: ${body}
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/70acff8c9a3019aa.
Report an issue: GitHub.