thedotmack/claude-mem · error

sync hub status: response must be an object

Error message

sync hub status: response must be an object

What it means

Thrown when the parsed JSON status body is not a non-null, non-array object (parsed is falsy, typeof !== 'object', or Array.isArray). The hub must return a JSON object; arrays, primitives, or null are rejected before any field is read.

Source

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

        },
        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);
      if (compareCanonicalDecimals(projectedSeq, headSeq) > 0) {
        throw new Error('sync hub status: projected_seq exceeds head_seq');
      }

View on GitHub (pinned to d768ba3643)

Solutions

  1. Log the parsed value's type to confirm what the hub actually returned.
  2. Confirm hubUrl and the request path are exactly /v1/sync/status on the correct hub.
  3. Verify hub protocol_version compatibility — the client expects a status object, not a list.
  4. If the hub changed its response shape, align client and hub versions.

Example fix

// before: hub returns an array for the status route
// GET /v1/sync/status -> [{"epoch":...}]
// after: hub returns the expected object
// GET /v1/sync/status -> {"protocol_version":2,"epoch":"...",...}
Defensive patterns

Strategy: validation

Type guard

function isHubStatusNotObject(e: unknown): boolean {
  return e instanceof Error && /sync hub status: response must be an object/i.test(e.message);
}

Try / catch

try { await cloudSync.status(); }
catch (e) { if (isHubStatusNotObject(e)) { markHubMisconfigured(e.message); return; } throw e; }

Prevention

When it happens

Trigger: The hub returned valid JSON but it was an array (e.g. a list endpoint by mistake), a JSON primitive (string/number/boolean), or null. Typically a routing mistake where a different endpoint answered /v1/sync/status.

Common situations: hubUrl routes to a generic collection endpoint that returns an array; a load balancer returning a JSON-formatted health string; the hub deployed a version where /v1/sync/status returns a different shape.

Related errors


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