thedotmack/claude-mem · error

sync hub pull: more must be boolean

Error message

sync hub pull: more must be boolean

What it means

Part of the same ChangesPage envelope validation in SyncClient: after protocol_version and ops pass, the pagination flag `more` must be a real boolean (typeof 'boolean'). The pull loop decides whether to fetch another page from `more`, so a missing or coerced value (undefined, 'true' as string, 1) is rejected rather than guessed.

Source

Thrown at src/services/sync/SyncClient.ts:558

        // response without the header (a degraded auth upstream 503ing
        // everything mid-incident — incidents correlate) is ambiguous and
        // must not exit poll mode, or the client would resume socket
        // churn for the whole outage.
        const syncMode = res.headers.get('X-Sync-Mode');
        if (syncMode !== null || res.ok) {
          this.onSyncModeHint(syncMode);
        }
        if (!res.ok) {
          const body = (await res.text().catch(() => '')).slice(0, 200);
          throw new Error(`sync hub pull ${res.status}: ${body}`);
        }
        const page = await res.json() as ChangesPage | null;
        if (!page || page.protocol_version !== 2 || !Array.isArray(page.ops)) {
          throw new Error('sync hub pull: malformed /changes response');
        }
        const epoch = assertCanonicalDecimal(page.epoch);
        assertCanonicalDecimal(page.head_seq);
        if (typeof page.more !== 'boolean') throw new Error('sync hub pull: more must be boolean');
        if (this.stopped) return;

        const decodedOps = decodeChanges(page.ops);
        const result = this.apply.applyOps(decodedOps, {
          epoch,
          requireContiguous: true,
        });
        pages++;

        if (result.epochReset) {
          // applyOps discarded the page and reset the cursor to 0; loop to
          // re-pull from the start (apply is idempotent by design).
          if (pages >= this.maxPagesPerCycle) return;
          continue;
        }

        // A page applied — the pipeline is healthy.
        this.failStreak = 0;

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Inspect the raw /changes body and confirm `more` is present as a JSON true/false.
  2. Upgrade the hub (and client) to matching releases so the v2 envelope is emitted verbatim.
  3. Remove any middleware that rewrites the response body between hub and client.
  4. For test mocks, return the full envelope: { protocol_version: 2, ops: [], more: false, ... }.
Defensive patterns

Strategy: validation

Validate before calling

const page = await res.json();
if (typeof page.more !== 'boolean') {
  throw new Error(`hub sent more=${JSON.stringify(page.more)} — expected boolean; hub/client version skew likely`);
}

Type guard

function hasBooleanMore(p: unknown): p is { more: boolean } {
  return !!p && typeof p === 'object' && typeof (p as Record<string, unknown>).more === 'boolean';
}

Try / catch

try {
  await syncClient.start();
} catch (e) {
  if (e instanceof Error && e.message.includes('more must be boolean')) {
    // Serialization layer is rewriting the envelope; compare raw body vs client expectation.
    await dumpRawChangesBody();
  }
}

Prevention

When it happens

Trigger: A hub build that omits `more`, serializes it as a string/number, or a serialization layer (custom JSON middleware, message pack bridge) that alters boolean representation; hand-written hub implementations forgetting the field.

Common situations: Hub and client version skew where the field was added/renamed; a response-transforming proxy coercing types; mocking the hub in tests with an incomplete fixture.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/105d0194b269f050. Report an issue: GitHub.