thedotmack/claude-mem · error · Error
sync hub status: response is not JSON
Error message
sync hub status: response is not JSON
What it means
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.
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.
Example fix
// before: blind probe
const res = await fetch(hubUrl + '/status');
// after: verify endpoint returns JSON before probing
const res = await fetch(hubUrl + '/status', { redirect: 'follow' });
if ((res.headers.get('content-type') || '').includes('application/json')) {
const status = await cloudSync.statusWithHubProbe();
} else {
logger.warn('hub status endpoint did not return JSON', { url: res.url });
} Defensive patterns
Strategy: validation
Validate before calling
const res = await fetch(hubUrl + '/status');
const ct = res.headers.get('content-type') || '';
if (!ct.includes('application/json')) {
throw new Error(`hub status returned non-JSON (${ct}): ${(await res.text()).slice(0, 100)}`);
} Try / catch
try {
const status = await cloudSync.statusWithHubProbe();
} catch (e) {
if (e.message === 'sync hub status: response is not JSON') {
logger.error('hub status endpoint not JSON — check URL/redirects/proxies', { url: hubUrl });
fallBackToLocalStatus();
} else throw e;
} Prevention
- 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
When it happens
Trigger: Hub returns text/html (proxy error page, login redirect), an empty body, or wrong Content-Type so response.json() fails to parse.
Common situations: 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.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- Failed to parse files JSON in hasDirectChildFile
- Failed to parse files_modified JSON
- Failed to parse files_read JSON
- Settings file is corrupted. Delete
- sync hub pull: malformed /changes response
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/70acff8c9a3019aa.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/CloudSync.ts:663
'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 d8bc9755e7)