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

  1. Start a new conversation/thread once the context grows large rather than pausing an overstuffed one.
  2. Reduce inline media — store attachments out-of-band and reference them by URL/id instead of embedding base64 in the checkpoint.
  3. Enable or tighten context pruning / message windowing so old tool outputs are dropped before pause.
  4. 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

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


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/1aef23db72fc1454. Report an issue: GitHub.