headroomlabs-ai/headroom · error · ValueError

max_batch_bytes must be at least min_batch_bytes

Error message

max_batch_bytes must be at least min_batch_bytes

What it means

Argument-validation ValueError from the batch-grouping function: max_batch_bytes < min_batch_bytes. The invariant matters because the greedy accumulator flushes when pending_bytes reaches the max; a ceiling below the floor means no batch could ever satisfy both constraints, so the function rejects the pair up front.

Source

Thrown at headroom/transforms/compression_batches.py:78

def build_compression_batches(
    entries: list[CompressionBatchEntry],
    *,
    min_batch_bytes: int,
    max_batch_bytes: int = DEFAULT_MAX_BATCH_BYTES,
    max_batch_units: int = DEFAULT_MAX_BATCH_UNITS,
) -> tuple[list[CompressionBatch], list[CompressionBatchEntry]]:
    """Greedily group compatible small units and skip under-floor tails.

    Callers retain the skipped entries as normal ``size_floor`` results. The
    function deliberately does not turn a unit larger than the configured
    batch ceiling into a singleton batch; those units belong to the existing
    independent compression path.
    """

    if min_batch_bytes <= 0:
        raise ValueError("min_batch_bytes must be positive")
    if max_batch_bytes < min_batch_bytes:
        raise ValueError("max_batch_bytes must be at least min_batch_bytes")
    if max_batch_units <= 0:
        raise ValueError("max_batch_units must be positive")

    batches: list[CompressionBatch] = []
    skipped: list[CompressionBatchEntry] = []
    pending: list[CompressionBatchEntry] = []
    pending_bytes = 0
    pending_key: tuple[object, ...] | None = None

    def flush() -> None:
        nonlocal pending, pending_bytes, pending_key
        if not pending:
            return
        if pending_bytes >= min_batch_bytes:
            batches.append(CompressionBatch(entries=tuple(pending), text_bytes=pending_bytes))
        else:
            skipped.extend(pending)
        pending = []

View on GitHub (pinned to 322425c43b)

Solutions

  1. Ensure max_batch_bytes >= min_batch_bytes (e.g. min=2048, max=32768).
  2. Add a config sanity check at startup: if not (0 < min <= max): fail with a clear message.
  3. Derive one from the other (max = 8*min) if you only want one knob.

Example fix

# before
group_batches(entries, min_batch_bytes=8192, max_batch_bytes=4096)

# after
group_batches(entries, min_batch_bytes=2048, max_batch_bytes=max(2048, cfg.max_batch_bytes))
Defensive patterns

Strategy: validation

Validate before calling

if not (0 < min_batch_bytes <= max_batch_bytes):
    raise ConfigError(f"batch bytes misconfigured: min={min_batch_bytes}, max={max_batch_bytes}")

Try / catch

try:
    group_batches(entries, min_batch_bytes=a, max_batch_bytes=b)
except ValueError as e:
    if "at least min_batch_bytes" in str(e):
        group_batches(entries, min_batch_bytes=a, max_batch_bytes=max(a, b))
    else:
        raise

Prevention

When it happens

Trigger: Calling the function with max_batch_bytes smaller than min_batch_bytes, e.g. min=4096/max=2048 — usually two independently configured knobs that drifted apart.

Common situations: Env vars or YAML tuned by different people at different times; lowering max_batch_bytes for memory reasons without re-checking min_batch_bytes; defaults overridden per-environment inconsistently.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/6205b9c36a6e4c48. Report an issue: GitHub.