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
- Inspect the raw /changes body and confirm `more` is present as a JSON true/false.
- Upgrade the hub (and client) to matching releases so the v2 envelope is emitted verbatim.
- Remove any middleware that rewrites the response body between hub and client.
- 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
- Return the envelope verbatim from hub handlers; never reconstruct it field-by-field in middleware.
- Cover the full envelope (including `more`) in contract tests on both hub and client.
- Upgrade hub and client together to keep the pagination contract aligned.
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
- sync hub pull: malformed /changes response
- sync hub status: response requires decimal-string epoch/head
- sync hub push: response is not JSON
- sync hub status: response is not JSON
- sync hub status: response must be an object
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/105d0194b269f050.
Report an issue: GitHub.