atuinsh/atuin · critical

overflowed maximum capacity

Error message

overflowed maximum capacity

What it means

`boxcar::Vec::push` reserves a slot by incrementing an `inflight` u64 counter and converting it to u32 with `.expect("overflowed maximum capacity")`. The conversion fails once 2^32 items are already in flight: the lock-free vector's u32 index space is full. This is a hard capacity ceiling - atuin-nucleo cannot index more than 4,294,967,295 items.

Source

Thrown at crates/atuin-nucleo/src/boxcar.rs:139

            // bucket is uninitialized
            if entries.is_null() {
                return None;
            }

            // safety: `location.entry` is always in bounds for it's bucket
            let entry = Bucket::<T>::get(entries, location.entry, self.columns);

            // safety: the entry is active
            (*entry).active.load(Ordering::Acquire).then(|| Entry::read(entry, self.columns))
        }
    }

    /// Appends an element to the back of the vector.
    pub fn push(&self, value: T, fill_columns: impl FnOnce(&T, &mut [Utf32String])) -> u32 {
        let index = self.inflight.fetch_add(1, Ordering::Release);
        // the inflight counter is a `u64` to catch overflows of the vector'scapacity
        let index: u32 = index.try_into().expect("overflowed maximum capacity");
        let location = Location::of(index);

        // eagerly allocate the next bucket if we are close to the end of this one
        if index == (location.bucket_len - (location.bucket_len >> 3)) {
            if let Some(next_bucket) = self.buckets.get(location.bucket as usize + 1) {
                Vec::get_or_alloc(next_bucket, location.bucket_len << 1, self.columns);
            }
        }

        // safety: `location.bucket` is always in bounds
        let bucket = unsafe { self.buckets.get_unchecked(location.bucket as usize) };
        let mut entries = bucket.entries.load(Ordering::Acquire);

        // the bucket has not been allocated yet
        if entries.is_null() {
            entries = Vec::get_or_alloc(bucket, location.bucket_len, self.columns);
        }

View on GitHub (pinned to 15fe1318f1)

Solutions

  1. Bound the number of pushed items to `u32::MAX` by tracking a running count and stopping before the ceiling
  2. Ensure the dataset is pushed exactly once - no re-injection loops that duplicate entries
  3. If you genuinely need more rows, shard across multiple matcher instances

Example fix

// before
matcher.push(item, |item, cols| fill(item, cols));

// after
const CEILING: u64 = u32::MAX as u64;
if pushed < CEILING {
    matcher.push(item, |item, cols| fill(item, cols));
    pushed += 1;
} else {
    // report capacity exhaustion instead of panicking
}
Defensive patterns

Strategy: validation

Validate before calling

// Bound total pushes to the u32 index space
const CEILING: u64 = u32::MAX as u64;
if injected_count >= CEILING {
    return Err(CapacityError);
}
matcher.push(item, fill);
injected_count += 1;

Type guard

fn fits_u32_index(count: u64) -> bool {
    count < u64::from(u32::MAX)
}

Prevention

When it happens

Trigger: Calling `push` (e.g. injecting into a `Nucleo` matcher) after roughly 4.29 billion prior pushes/extends on the same instance, such as a loop that re-injects the whole dataset per keystroke without resetting.

Common situations: Unreachable for atuin's real history workloads; appears in stress tests, fuzzing, or bugs that re-push the entire dataset repeatedly.

Related errors


AI-assisted analysis of atuinsh/atuin@15fe1318f1 (2026-08-19). Data as JSON: /api/errors/f8a9b4a628eebca4. Report an issue: GitHub.