thedotmack/claude-mem · error · Error

sync hub status: response must be an object

Error message

sync hub status: response must be an object

What it means

After parsing, probeHubStatus validates that the hub status payload is a JSON object. Arrays, null, or primitives are rejected with 'sync hub status: response must be an object' because the sync-hub protocol record is expected to be a flat object of protocol fields.

Solutions

  1. Check the hub version; upgrade or downgrade so it serves the sync-hub status protocol object.
  2. Inspect the raw response body to see what shape the hub actually returned.
  3. Confirm you are hitting the status endpoint, not a list/collections endpoint.
  4. Fix custom/mock hub implementations to return a JSON object at the top level.

Example fix

// before
const parsed = JSON.parse(body);
// after (hub-side or client pre-check)
const parsed = JSON.parse(body);
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
  throw new Error('hub status payload must be a JSON object, got: ' + JSON.stringify(parsed).slice(0, 100));
}
Defensive patterns

Strategy: validation

Validate before calling

const res = await fetch(hubUrl + '/status');
const parsed = await res.json();
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
  throw new Error('hub returned non-object status payload');
}

Type guard

function isStatusRecord(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Try / catch

try {
  const status = await cloudSync.statusWithHubProbe();
} catch (e) {
  if (e.message === 'sync hub status: response must be an object') {
    logger.error('hub protocol shape mismatch — check hub version', { bodySniff: lastRawBody });
  } else throw e;
}

Prevention

When it happens

Trigger: Hub returns a JSON array, null, a string/number, or a top-level structure that is not a plain object.

Common situations: Hub version mismatch returning an older/different payload shape, a load balancer or mock returning a list, a custom hub implementation deviating from the protocol.

Related errors


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

Appendix: source

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

        },
        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 d8bc9755e7)