atuinsh/atuin · critical

output-capture reclaim task panicked

Error message

output-capture reclaim task panicked

What it means

This is the `.expect()` on the `JoinHandle` of the `spawn_blocking` closure in `FjallBackendInner::reclaim` (crates/atuin-daemon/src/output_capture/backend/fjall/mod.rs:172), which deletes oldest entries until at least `reclaim_bytes` are freed. It surfaces a panic in that blocking task — most plausibly the `unreachable!("reclaim performs no tracked reads, so it can never conflict")` branch on an unexpected `fjall::Conflict`, a panic while iterating keyspace entries, or a JoinError from task cancellation.

Source

Thrown at crates/atuin-daemon/src/output_capture/backend/fjall/mod.rs:172

                freed = freed.saturating_add(u64::try_from(value.len()).unwrap_or(u64::MAX));
                if freed >= reclaim_bytes {
                    break;
                }
            }

            match tx.commit().map_err(|err| DeleteOutputError::Storage(Box::new(err)))? {
                Ok(()) => {
                    dirty.store(true, Ordering::Release);
                    Ok(freed)
                }
                Err(fjall::Conflict) => {
                    unreachable!("reclaim performs no tracked reads, so it can never conflict")
                }
            }
        })
        .await
        .expect("output-capture reclaim task panicked")
    }
}

/// Task responsible for flushing fjall data buffered in memory onto the disk.
#[derive(Debug)]
struct Flusher {
    /// Handle to the background task.
    task: JoinHandle<()>,
}

impl Flusher {
    /// How often to try to flush.
    ///
    /// We'd expect flush itself to take anywhere between 1-10ms, so this is plenty of overhead.
    const SYNC_INTERVAL: Duration = Duration::from_secs(5);

    pub fn spawn(inner: Arc<FjallBackendInner>) -> Self {
        let task = tokio::task::spawn(async move {

View on GitHub (pinned to c0c717ab04)

Solutions

  1. Check the fjall crate version for changes to Conflict/transaction semantics; align atuin-daemon with a compatible fjall release
  2. Capture the JoinError panic payload to identify the true panic site (conflict branch vs iterator) and file/fix accordingly
  3. Restart the daemon and retry reclaim; if it recurs, move the data dir aside and let the store rebuild
  4. Reduce store size manually (remove old captures) so reclaim iterates fewer entries while the underlying issue is investigated

Example fix

// before
Err(fjall::Conflict) => {
    unreachable!("reclaim performs no tracked reads, so it can never conflict")
}
// after: report instead of panicking
Err(fjall::Conflict) => Err(DeleteOutputError::Storage(
    "unexpected conflict during reclaim".into(),
)),
Defensive patterns

Strategy: retry

Validate before calling

// Only invoke reclaim with a meaningful target; the store itself
// short-circuits 0, and callers should rate-limit GC passes.
if reclaim_bytes > 0 && store.estimated_disk_usage() > high_watermark {
    store.reclaim(reclaim_bytes).await?;
}

Type guard

fn reclaimed_ok(r: &Result<u64, DeleteOutputError>) -> bool {
    matches!(r, Ok(_))
}

Try / catch

// Wrap the GC pass so a panic is contained and retried later:
match tokio::spawn(backend.reclaim(bytes)).await {
    Ok(Ok(freed)) => log_freed(freed),
    Ok(Err(e)) | Err(_) => defer_gc_with_backoff(e),
}

Prevention

When it happens

Trigger: Calling `reclaim(reclaim_bytes)` when disk-usage pressure triggers it (DiskUsageLimit) and: (1) the write transaction unexpectedly commits with `fjall::Conflict`, firing `unreachable!()`; (2) `guard.into_inner()` or the iterator panics on a corrupt segment; (3) the blocking task is cancelled during daemon shutdown.

Common situations: The daemon's automatic disk-space reclamation kicking in on a large output-capture store; concurrent GC and writer activity under a fjall release whose optimistic-transaction semantics differ from the assumption baked into the `unreachable!()`; corrupted fjall data directory.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of atuinsh/atuin@c0c717ab04 (2026-09-12). Data as JSON: /api/errors/46b972979d9c2d87. Report an issue: GitHub.