{"record":{"id":"105d0194b269f050","repo":"thedotmack/claude-mem","slug":"sync-hub-pull-more-must-be-boolean","errorCode":null,"errorMessage":"sync hub pull: more must be boolean","messagePattern":"sync hub pull: more must be boolean","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sync/SyncClient.ts","lineNumber":558,"sourceCode":"        // response without the header (a degraded auth upstream 503ing\n        // everything mid-incident — incidents correlate) is ambiguous and\n        // must not exit poll mode, or the client would resume socket\n        // churn for the whole outage.\n        const syncMode = res.headers.get('X-Sync-Mode');\n        if (syncMode !== null || res.ok) {\n          this.onSyncModeHint(syncMode);\n        }\n        if (!res.ok) {\n          const body = (await res.text().catch(() => '')).slice(0, 200);\n          throw new Error(`sync hub pull ${res.status}: ${body}`);\n        }\n        const page = await res.json() as ChangesPage | null;\n        if (!page || page.protocol_version !== 2 || !Array.isArray(page.ops)) {\n          throw new Error('sync hub pull: malformed /changes response');\n        }\n        const epoch = assertCanonicalDecimal(page.epoch);\n        assertCanonicalDecimal(page.head_seq);\n        if (typeof page.more !== 'boolean') throw new Error('sync hub pull: more must be boolean');\n        if (this.stopped) return;\n\n        const decodedOps = decodeChanges(page.ops);\n        const result = this.apply.applyOps(decodedOps, {\n          epoch,\n          requireContiguous: true,\n        });\n        pages++;\n\n        if (result.epochReset) {\n          // applyOps discarded the page and reset the cursor to 0; loop to\n          // re-pull from the start (apply is idempotent by design).\n          if (pages >= this.maxPagesPerCycle) return;\n          continue;\n        }\n\n        // A page applied — the pipeline is healthy.\n        this.failStreak = 0;","sourceCodeStart":540,"sourceCodeEnd":576,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/sync/SyncClient.ts#L540-L576","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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, ... }."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const page = await res.json();\nif (typeof page.more !== 'boolean') {\n  throw new Error(`hub sent more=${JSON.stringify(page.more)} — expected boolean; hub/client version skew likely`);\n}","typeGuard":"function hasBooleanMore(p: unknown): p is { more: boolean } {\n  return !!p && typeof p === 'object' && typeof (p as Record<string, unknown>).more === 'boolean';\n}","tryCatchPattern":"try {\n  await syncClient.start();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('more must be boolean')) {\n    // Serialization layer is rewriting the envelope; compare raw body vs client expectation.\n    await dumpRawChangesBody();\n  }\n}","preventionTips":["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."],"tags":["sync","http","protocol-validation","pagination","json"],"backgroundTag":"response-schema-validation","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}