thedotmack/claude-mem · error

sync hub status ${response.status}: ${body}

Error message

sync hub status ${response.status}: ${body}

What it means

Thrown by probeHubStatus when the GET to ${hubUrl}/v1/sync/status returned a non-OK HTTP status. The response body is read (up to 200 chars, swallow read errors) and included verbatim. X-Sync-Mode header is still emitted before throwing (presence regardless of status). This is the hub telling the client something is wrong at the HTTP layer (auth, rate limit, server error).

Source

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

  private async probeHubStatus(): Promise<void> {
    let checkedAt = Date.now();
    try {
      const response = await this.fetchImpl(`${this.hubUrl}/v1/sync/status`, {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${this.token}`,
          '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'

View on GitHub (pinned to d768ba3643)

Solutions

  1. Read the status code and body in the message — 401/403 means re-authenticate; 404 means fix hubUrl; 5xx means hub-side issue.
  2. Verify the token is current and the X-User-Id/X-Device-Id headers match the account/device the hub expects.
  3. Confirm hubUrl points at the correct hub environment (production vs staging) and includes the right base path.
  4. For 429, back off the status polling cadence.
  5. If 5xx persists, check the hub status page / contact the hub operator.

Example fix

// before: stale token yields 401 on every probe
hubUrl = 'https://sync.example.com'; token = oldToken;
// after: refreshed token and correct hub base path
hubUrl = 'https://sync.example.com'; token = await refreshToken();
Defensive patterns

Strategy: try-catch

Type guard

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

Try / catch

try { await cloudSync.status(); }
catch (e) {
  if (isHubStatusHttpError(e)) {
    const code = Number(e.message.match(/status (\d{3})/)?.[1]);
    if (code === 401 || code === 403) { await refreshToken(); await cloudSync.status(); }
    else if (code === 429) { await sleep(backoffMs); await cloudSync.status(); }
    else { markHubUnreachable(e.message); }
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: The sync hub returns 4xx/5xx for the status probe: 401/403 for a bad or expired token (Authorization: Bearer ${token}), 404 for a wrong hubUrl, 429 for rate limiting, 5xx for hub outage. The X-User-Id/X-Device-Id headers did not satisfy the hub.

Common situations: Token expired or revoked; hubUrl misconfigured (wrong host/path/stage); user/device id headers missing or wrong; hub under maintenance returning 503; client version pointing at a hub that deprecated /v1/sync/status.

Related errors


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