thedotmack/claude-mem · error

sync hub push ${res.status}: ${body}

Error message

sync hub push ${res.status}: ${body}

What it means

Thrown by the push response handler when the POST to ${hubUrl}/v1/sync/ops returned a non-OK status. The body is read (up to 200 chars) and included. X-Sync-Mode header is emitted on presence or OK, but absence on an error response is deliberately NOT emitted as a clear (per the documented asymmetric contract) so a degraded-auth 503 does not read as 'switch cleared'.

Source

Thrown at src/services/sync/CloudSync.ts:969

      },
      body: requestBody,
      signal: AbortSignal.timeout(this.requestTimeoutMs),
    });
    // Mode hint BEFORE the ok-check: the kill-switch header rides error
    // responses too, and a client that only learned the mode from happy
    // 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);

View on GitHub (pinned to d768ba3643)

Solutions

  1. Read the status code and body in the message — 401/403 → re-auth; 409 → re-sync from status; 429 → back off; 5xx → retry with backoff.
  2. Re-run probeHubStatus to refresh epoch/head_seq before retrying the push, in case of a 409 conflict.
  3. Verify the token is still valid and X-User-Id/X-Device-Id are correct.
  4. For 429, reduce push frequency / batch more aggressively.
  5. For 5xx, retry with exponential backoff and surface a hub-outage indication to the user.

Example fix

// before: push on a stale epoch hits 409 and aborts
await cloudSync.pushOps(ops);
// after: refresh status then retry on conflict
try { await cloudSync.pushOps(ops); }
catch (e) { if (/push 409/.test(e.message)) { await cloudSync.status(); await backoff(); await cloudSync.pushOps(ops); } else throw e; }
Defensive patterns

Strategy: retry

Type guard

function isPushHttpError(e: unknown): boolean {
  return e instanceof Error && /^sync hub push \d{3}:/i.test(e.message);
}

Try / catch

try { await cloudSync.pushOps(ops); }
catch (e) {
  if (isPushHttpError(e)) {
    const code = Number(e.message.match(/push (\d{3})/)?.[1]);
    if (code === 409) { await cloudSync.status(); await sleep(500); await cloudSync.pushOps(ops); }
    else if (code === 429 || code >= 500) { await sleep(backoffMs); await cloudSync.pushOps(ops); }
    else { throw e; }
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: The hub rejected the push: 401/403 (token/user/device invalid), 409 (sequence conflict / epoch mismatch), 413 (body too large, though 115 guards client-side), 422 (malformed ops), 429 (rate limit), 5xx (hub outage).

Common situations: Token expired between status and push; out-of-sync epoch/head_seq causing a 409; hub rate-limiting a chatty client; partial hub outage; client pushing ops the hub considers malformed after a protocol change.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/be29f97a63d19ced. Report an issue: GitHub.