atuinsh/atuin · error
output-capture write task panicked
Error message
output-capture write task panicked
What it means
The fjall-backed output capture runs its write path inside `tokio::task::spawn_blocking` and `.expect()`s the JoinHandle. Panicking means the spawned blocking task itself panicked (not a storage error — those are mapped to `CaptureError`). The storage engine thread crashed mid-write.
Source
Thrown at crates/atuin-daemon/src/output_capture/backend/fjall/mod.rs:80
if tx
.contains_key(&keyspace, key)
.map_err(|err| CaptureError::Storage(Box::new(err)))?
{
return Err(CaptureError::AlreadyExists);
}
tx.insert(&keyspace, key, value);
match tx.commit().map_err(|err| CaptureError::Storage(Box::new(err)))? {
Ok(()) => {
dirty.store(true, Ordering::Release);
Ok(())
}
// Another writer committed this key first, so it's already captured.
Err(fjall::Conflict) => Err(CaptureError::AlreadyExists),
}
})
.await
.expect("output-capture write task panicked")
}
async fn get(&self, id: HistoryId) -> Result<Option<CommandCapture>, GetOutputError> {
let keyspace = self.keyspace.clone();
let key = ActiveSchema::serialize_key(id).expect("history id serialization is infallible");
tokio::task::spawn_blocking(move || {
match keyspace.get(key).map_err(|err| GetOutputError::Storage(Box::new(err)))? {
Some(slice) => {
let capture = ActiveSchema::deserialize_value(slice.to_vec())
.expect("stored value is a valid CommandCapture");
Ok(Some(capture))
}
None => Ok(None),
}
})
.await
.expect("output-capture read task panicked")View on GitHub (pinned to c0c717ab04)
Solutions
- Check disk space and filesystem health on the directory holding the fjall keyspace
- Inspect/repair or delete the corrupted fjall data directory (it can be rebuilt from history)
- Look for the underlying panic message in logs — the expect here masks it unless captured
- Update fjall to the latest patched version
- Report the inner panic to Atuin if data is intact and reproducible
Example fix
// before
.await.expect("output-capture write task panicked")
// after
.await.map_err(|e| CaptureError::Storage(Box::new(e)))? Defensive patterns
Strategy: retry
Try / catch
// Handle JoinError from spawn_blocking:
match handle.await {
Ok(res) => res,
Err(join_err) => return Err(CaptureError::Storage(Box::new(join_err))),
} Prevention
- Monitor disk space on the fjall data directory
- Always shut the daemon down cleanly to avoid log-segment corruption
- Keep fjall updated to patched releases
- Log inner panics (catch_unwind or JoinError payload) for diagnosis
When it happens
Trigger: Any `capture()` call where the inner closure panics — e.g. a fjall internal panic on corrupted log segments, allocation failure, or panic in serialization inside the blocking task.
Common situations: Disk-full or I/O errors surfacing as panics inside fjall; corrupted fjall data directory after an unclean shutdown; OOM killer killing threads mid-write; fjall version bugs.
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
- output-capture reclaim task panicked
- persistence task shouldn't panic
- issue in stats average query
AI-assisted analysis of atuinsh/atuin@c0c717ab04 (2026-09-12).
Data as JSON: /api/errors/036916a708308af8.
Report an issue: GitHub.