thedotmack/claude-mem · error · Error
sync hub push invariant: request exceeds
Error message
sync hub push invariant: request exceeds ${MAX_BODY_BYTES} encoded bytes What it means
pushOps enforces a hard limit on the encoded push request body (MAX_BODY_BYTES) before POSTing ops to the hub. If JSON.stringify of the batch exceeds the limit, the client refuses to send it, since the hub would reject oversized bodies. This is a client-side guard of an internal invariant about batch size.
Solutions
- Reduce the ops batch size — chunk the WireOp[] into batches under MAX_BODY_BYTES and push sequentially.
- Flush the outbox more frequently so batches stay small.
- Inspect the outbox for abnormally large ops (e.g. huge content payloads) and trim them at the source.
- If legitimate payloads need more room, raise MAX_BODY_BYTES consistently on client and hub.
Example fix
// before
await client.pushOps(allOps);
// after
for (const chunk of chunkOps(allOps, MAX_OPS_PER_BATCH)) {
await client.pushOps(chunk);
} Defensive patterns
Strategy: validation
Validate before calling
const body = JSON.stringify({ protocol_version: 2, ops });
if (Buffer.byteLength(body, 'utf8') <= 4 * 1024 * 1024) await client.pushOps(ops); Try / catch
try { await client.pushOps(ops); } catch (e) { if (e.message.includes('exceeds') && e.message.includes('bytes')) { /* split batch and retry */ } } Prevention
- Chunk op batches to a conservative size before pushing.
- Drain the outbox on a short interval so batches never balloon.
- Monitor outbox size and alert when it grows unbounded.
When it happens
Trigger: Calling the push path with a WireOp[] batch whose JSON body exceeds MAX_BODY_BYTES — e.g. many queued ops accumulated in sync_content_outbox flushed in one request.
Common situations: Long offline periods let the outbox grow; a large batch is drained in one push; deletes or large content updates inflate op payloads; MAX_BODY_BYTES was lowered on a hub/proxy upgrade while the client still batches aggressively.
Understand the failure class
Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.
Related errors
- cloud sync identity unavailable; refusing an unreplicated…
- cloud sync unavailable; refusing an unreplicated delete
- pull
- push
- sync hub pull: malformed /changes response
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/9818d4e5743804e1.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/CloudSync.ts:1168
this.db.prepare(`
UPDATE ${table} SET synced_at = NULL
WHERE id = ? AND origin_device_id IS NULL AND (synced_at IS NULL OR synced_at < 0)
`).run(row.origin_local_id);
}
});
tx();
logger.error('CLOUD_SYNC', 'Dropped stale content outbox snapshot; local row left unsynced for resnapshot', {
kind: row.kind,
originLocalId: row.origin_local_id,
entityRev: row.entity_rev,
reason,
});
}
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 d8bc9755e7)