thedotmack/claude-mem · error
sync hub push invariant: request exceeds ${MAX_BODY_BYTES} e
Error message
sync hub push invariant: request exceeds ${MAX_BODY_BYTES} encoded bytes What it means
Thrown by pushOps before the HTTP request is sent, when JSON.stringify({protocol_version:2, ops}) exceeds MAX_BODY_BYTES (4,000,000). This is a client-side invariant guard: the batch-sizing logic upstream (pushRequestBytes checks during batching) should keep every push under the cap, so hitting this throw means the batcher and the actual serialized size disagree — a code/path bug rather than normal operation.
Source
Thrown at src/services/sync/CloudSync.ts:941
}
}
/** POST one batch to the hub and stamp/delete on ack. */
private async sendOps(ops: WireOp[]): Promise<void> {
const response = await this.pushOps(ops);
// stop() while the POST was in flight: the DB may already be closing, so
// skip the stamp. The hub dedupes on (origin_device, kind, origin_id,
// rev), so re-pushing these ops on next start is harmless.
if (this.stopped) return;
this.validatePushResponse(response, ops);
this.stampAcked(response.acked, ops);
this.emitHeadSeq(response.head_seq);
}
private async pushOps(ops: WireOp[]): Promise<PushResponse> {
const requestBody = JSON.stringify({ protocol_version: 2, ops });
if (Buffer.byteLength(requestBody, 'utf8') > MAX_BODY_BYTES) {
throw new Error(`sync hub push invariant: request exceeds ${MAX_BODY_BYTES} encoded bytes`);
}
const res = await this.fetchImpl(`${this.hubUrl}/v1/sync/ops`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
'X-User-Id': this.userId,
'X-Device-Id': this.deviceId,
...(this.deviceName ? { 'X-Device-Name': this.deviceName } : {}),
},
body: requestBody,
signal: AbortSignal.timeout(this.requestTimeoutMs),
});
// Mode hint BEFORE the ok-check: the kill-switch header rides error
// responses too, and a client that only learned the mode from happy
// paths would keep hammering the socket through an incident.
// Asymmetric on purpose (SyncClient.onSyncModeHint contract): header
// PRESENCE is emitted regardless of status; header ABSENCE is onlyView on GitHub (pinned to d768ba3643)
Solutions
- Inspect the ops being pushed for an abnormally large payload (huge observation/summary text) and cap individual op size upstream.
- Re-check pushRequestBytes logic — ensure it measures the same encoding (utf8 byte length) the guard uses.
- Reduce the batch size so serialized output stays comfortably under 4MB.
- If a single op legitimately exceeds the cap, chunk the content or store it out-of-band and sync a reference.
- Treat this as a defect to fix in the batching path, not a runtime condition to retry.
Example fix
// before: batcher counts ops, not bytes, so one giant op slips through batch.push(op); // no size check // after: batcher enforces the same byte budget as pushOps if (pushRequestBytes(buf + JSON.stringify(op), batch.length+1) > MAX_BODY_BYTES) flush(); batch.push(op);
Defensive patterns
Strategy: validation
Validate before calling
// Enforce the same 4MB cap when building the batch, not just when sending it
const MAX_BODY_BYTES = 4_000_000;
function fitsPushBudget(ops: unknown[]): boolean {
return Buffer.byteLength(JSON.stringify({ protocol_version: 2, ops }), 'utf8') <= MAX_BODY_BYTES;
} Type guard
function isPushBodyTooLarge(e: unknown): boolean {
return e instanceof Error && /sync hub push invariant: request exceeds.*encoded bytes/i.test(e.message);
} Try / catch
// This is a code defect, not a runtime retry condition
if (isPushBodyTooLarge(e)) { log.error('batcher size accounting is broken', e); /* split batch + re-flush */ } Prevention
- Measure batch byte size with the same Buffer.byteLength(JSON.stringify(...)) the guard uses.
- Cap individual op payload size before adding to a batch.
- Keep the batcher's estimator in sync with the WireOp serialized shape.
- Add a unit test asserting no legal batch exceeds MAX_BODY_BYTES.
When it happens
Trigger: A batch of WireOp ops was assembled whose serialized JSON exceeds 4MB; the upstream batch split logic (lines ~740, ~820 using pushRequestBytes > MAX_BODY_BYTES) failed to bound the batch, or ops were added after the size check.
Common situations: A single op with an enormous embedded document/observation payload that alone exceeds 4MB; a bug in the batcher's size accounting (e.g. counting ops but not JSON overhead); a change to WireOp shape that inflated serialized size beyond the estimator.
Related errors
- sync hub status: projected_seq exceeds head_seq
- sync hub push ${res.status}: ${body}
- sync hub push: response is not JSON
- sync hub push: response missing acked array
- sync hub push: response requires decimal-string head_seq/pro
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/9818d4e5743804e1.
Report an issue: GitHub.