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

  1. Check disk space and filesystem health on the directory holding the fjall keyspace
  2. Inspect/repair or delete the corrupted fjall data directory (it can be rebuilt from history)
  3. Look for the underlying panic message in logs — the expect here masks it unless captured
  4. Update fjall to the latest patched version
  5. 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

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


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