danny-avila/LibreChat · critical · CheckpointTooLargeError
CHECKPOINT_TOO_LARGE
CHECKPOINT_TOO_LARGE
Error message
Checkpoint state is ${mb(bytes)} MB, over the ${mb(limit)} MB limit for a durable pause. This conversation carries too much state to pause for input — large tool outputs or inlined media are the usual cause. Start a new conversation or reduce context. What it means
CheckpointTooLargeError (code CHECKPOINT_TOO_LARGE) is thrown by assertCheckpointFitsDocument on the HITL (human-in-the-loop) pause path when the serialized checkpoint + serialized metadata + raw metadata BSON size exceeds hardLimitBytes (the MongoDB 16 MB single-document ceiling). The check exists so an oversize pause fails legibly instead of producing a raw BSONObjectTooLarge from Mongo. The anchoring write row is left for the pre-run prune and TTL to reclaim.
Source
Thrown at packages/api/src/agents/checkpointer.ts:490
// prevent. Add the raw metadata's BSON size; the headroom now only has to
// cover ids and BSON framing.
const [, serializedCheckpoint] = await this.serde.dumpsTyped(checkpoint);
const [, serializedMetadata] = await this.serde.dumpsTyped(metadata);
const metadataSearchBytes = mongoose.mongo.BSON.calculateObjectSize(
metadata as unknown as Record<string, unknown>,
);
const bytes =
serializedCheckpoint.byteLength + serializedMetadata.byteLength + metadataSearchBytes;
const threadId = config.configurable?.thread_id as string | undefined;
const mb = (n: number): string => (n / 1024 / 1024).toFixed(1);
if (bytes > this.hardLimitBytes) {
// The anchoring write row was already persisted by `putWrites`; the pre-run prune and Mongo
// TTL reclaim it. Drop any parked bookkeeping so it doesn't linger in memory.
this.bufferedBookkeeping.delete(checkpoint.id);
logger.error(
`[checkpointer] HITL checkpoint for thread ${threadId ?? 'unknown'} is ${mb(bytes)} MB, over the ${mb(this.hardLimitBytes)} MB durable-pause limit; refusing the write (a document past 16 MB cannot be stored in MongoDB).`,
);
throw new CheckpointTooLargeError(bytes, this.hardLimitBytes, threadId);
}
if (bytes >= this.warnBytes) {
logger.warn(
`[checkpointer] HITL checkpoint for thread ${threadId ?? 'unknown'} is ${mb(bytes)} MB, past the ${mb(this.warnBytes)} MB soft threshold (hard limit ${mb(this.hardLimitBytes)} MB) — approaching MongoDB's single-document ceiling.`,
);
return;
}
logger.debug(
`[checkpointer] Persisting HITL checkpoint for thread ${threadId ?? 'unknown'}: ${bytes} bytes`,
);
}
}
/**
* Evict genuinely-stale entries from a fate-tracking map once it is crowded
* ({@link WRITE_ANCHOR_SWEEP_THRESHOLD}). Entries from a crashed run (older than
* {@link WRITE_ANCHOR_STALE_MS}) are reclaimed; recent in-flight entries never are.
*/View on GitHub (pinned to 5ff282f900)
Solutions
- Start a new conversation/thread once the context grows large rather than pausing an overstuffed one.
- Reduce inline media — store attachments out-of-band and reference them by URL/id instead of embedding base64 in the checkpoint.
- Enable or tighten context pruning / message windowing so old tool outputs are dropped before pause.
- If running a custom checkpointer, raise hardLimitBytes only up to MongoDB's true 16 MB ceiling — there is no headroom above it.
Example fix
// before — agent accumulates a 20 MB tool result then pauses for input
// after — trim large tool outputs before the pause
const trimmed = trimToolOutputsToSummaries(messages, { maxBytes: 1_000_000 });
await runAgent({ ...state, messages: trimmed });
// and store large artifacts out-of-band, referencing them by id Defensive patterns
Strategy: validation
Validate before calling
// estimate checkpoint size before pausing
function estimateCheckpointBytes(messages: unknown[]): number {
return JSON.stringify(messages).length;
}
const HARD_LIMIT = 16 * 1024 * 1024;
if (estimateCheckpointBytes(state.messages) > HARD_LIMIT * 0.9) {
throw new Error('Refusing to pause: checkpoint would exceed MongoDB 16 MB limit');
} Type guard
function isCheckpointTooLargeError(e: unknown): boolean {
return e instanceof Error && (e as { code?: string }).code === 'CHECKPOINT_TOO_LARGE';
} Try / catch
try {
await agent.invoke(state, { configurable: { thread_id } });
} catch (error) {
if (isCheckpointTooLargeError(error)) {
// surface to the user; offer to start a new conversation
return { fatal: true, reason: 'context_too_large_to_pause' };
}
throw error;
} Prevention
- Store large media out-of-band; never inline base64 in messages.
- Enable message-windowing / context pruning so old tool outputs drop before pause.
- Summarize large tool results before they enter conversation history.
- Start a new thread once context grows large instead of pausing.
When it happens
Trigger: An agent run that pauses for human input where the conversation state — large tool outputs, inlined base64 media, accumulated message history, or metadata.writes holding a big tool result — pushes the checkpoint document past the 16 MB BSON limit.
Common situations: Long-running agents that accumulate large file/image attachments; tool calls that return very large JSON; conversations with many rounds where context compaction has not pruned; base64-encoded images stored inline in messages.
Related errors
- Agent not found
- Subagent graph exceeds the maximum of ${MAX_SUBAGENT_GRAPH_N
- Subagent run configuration exceeds the maximum of ${MAX_SUBA
- Subagent ${agentId} changed before it could be initialized.
- You no longer have access to subagent ${agentId}.
AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12).
Data as JSON: /api/errors/1aef23db72fc1454.
Report an issue: GitHub.