thedotmack/claude-mem · error · Error
sync hub push: response is not JSON
Error message
sync hub push: response is not JSON
What it means
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.
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.
Example fix
// before hubUrl: process.env.CLAUDE_MEM_CLOUD_SYNC_HUB_URL // after // ensure it is the API base, e.g. hubUrl: 'https://sync.example.com' // serves JSON at /v1/sync/ops
Defensive patterns
Strategy: try-catch
Type guard
function isPushResponse(v: unknown): v is { acked: unknown[]; head_seq?: number } {
return typeof v === 'object' && v !== null && Array.isArray((v as any).acked);
} Try / catch
try { await client.pushOps(ops); } catch (e) { if (e.message === 'sync hub push: response is not JSON') { /* check hubUrl / proxy config */ } } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Misconfigured reverse proxy intercepting requests; hub mis-deployed behind an auth wall; wrong hubUrl pointing at a non-sync server; network appliance rewriting responses.
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
- sync hub pull: malformed /changes response
- sync hub pull: more must be boolean
- canonical content
- cloud sync identity unavailable; refusing an unreplicated…
- cloud sync unavailable; refusing an unreplicated delete
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/71340f66efa54e49.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/CloudSync.ts:1202
// paths would keep hammering the socket through an incident.
// Asymmetric on purpose (SyncClient.onSyncModeHint contract): header
// PRESENCE is emitted regardless of status; header ABSENCE is only
// emitted (as null = "cleared") from an OK response — absence on an
// error response is ambiguous (a degraded auth upstream 503s without
// the funnel) and must not read as "switch cleared".
const syncMode = res.headers.get('X-Sync-Mode');
if (syncMode !== null || res.ok) {
this.emitSyncMode(syncMode);
}
if (!res.ok) {
const body = (await res.text().catch(() => '')).slice(0, 200);
throw new Error(`sync hub push ${res.status}: ${body}`);
}
let parsed: unknown;
try {
parsed = await res.json();
} catch {
throw new Error('sync hub push: response is not JSON');
}
const acked = (parsed as { acked?: unknown } | null)?.acked;
if (!Array.isArray(acked)) {
throw new Error('sync hub push: response missing acked array');
}
const headSeq = (parsed as { head_seq?: unknown }).head_seq;
const projectedSeq = (parsed as { projected_seq?: unknown }).projected_seq;
if (typeof headSeq !== 'string' || typeof projectedSeq !== 'string') {
throw new Error('sync hub push: response requires decimal-string head_seq/projected_seq');
}
assertCanonicalDecimal(headSeq);
assertCanonicalDecimal(projectedSeq);
const validatedAcked = acked.map((value, index): AckedOp => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`sync hub push: acked[${index}] must be an object`);
}
const item = value as Record<string, unknown>;
if (View on GitHub (pinned to d8bc9755e7)