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
- Check the fjall crate version for changes to Conflict/transaction semantics; align atuin-daemon with a compatible fjall release
- Capture the JoinError panic payload to identify the true panic site (conflict branch vs iterator) and file/fix accordingly
- Restart the daemon and retry reclaim; if it recurs, move the data dir aside and let the store rebuild
- 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
- Set DiskUsageLimit conservatively so reclaim runs before the store is very large
- Pin compatible fjall versions; audit any fjall upgrade touching optimistic transactions
- Run reclaim when write traffic is low to avoid concurrent-commit surprises
- Check disk health; iterator panics during reclaim often follow I/O corruption
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
- output-capture read task panicked
- output-capture delete task panicked
- persistence task shouldn't panic
- output-capture write task panicked
- failed to register sigterm handler
AI-assisted analysis of atuinsh/atuin@c0c717ab04 (2026-09-12).
Data as JSON: /api/errors/46b972979d9c2d87.
Report an issue: GitHub.