atuinsh/atuin · error
still present: re-checked under the finalization mutex every
Error message
still present: re-checked under the finalization mutex every remover holds
What it means
In the history journal's `cancel()`, an entry for `history_id` is expected to exist in `active_cmds` because prior logic re-checks presence under the finalization mutex that all removers hold. Panicking here means that internal invariant was violated — something removed the entry without holding the same lock or the re-check has a logic bug.
Source
Thrown at crates/atuin-daemon/src/history_journal.rs:439
.ok_or(CmdCancelError::NotFound(history_id))?;
let _guard = lock.lock().await;
if !self.active_cmds.contains_key(&history_id) {
return Err(CmdCancelError::NotFound(history_id));
}
if let Err(err) = self.output_capture.remove([history_id]).await {
tracing::error!(
%history_id,
?err,
"failed to discard the captured output of a cancelled command"
);
}
let (_id, cmd) = self
.active_cmds
.remove(&history_id)
.expect("still present: re-checked under the finalization mutex every remover holds");
let _ = self.broadcast.send(CmdEvent::Cancelled(cmd.history));
Ok(())
}
/// Delete the given history entries from Atuin's memory completely, including any captured
/// output they have, and refuse output for them from then on.
///
/// `search_settings` is needed to rebuild the search index's frecency map after the deletion,
/// so the swapped-in index has correct rankings immediately rather than after the next refresh.
///
/// Returns how many history entries Atuin forgot.
///
/// This function is serialized on the **bundle** of the `ids` you're passing in, eg. if you
/// pass `ids = [1, 2]` and then in parallel another delete call with `ids = [2, 3]`, the latter
/// will wait for the former request to completely go through.
pub async fn delete(View on GitHub (pinned to c0c717ab04)
Solutions
- Report this as a bug to Atuin — it indicates an internal locking invariant violation
- Check recently modified daemon code paths that touch `active_cmds` for missed mutex acquisition
- Capture a stack trace with RUST_BACKTRACE=1 and attach it to the issue report
- As a workaround, restart the daemon to clear corrupted in-memory state
Example fix
// before
let (_id, cmd) = self.active_cmds.remove(&history_id)
.expect("still present: re-checked under the finalization mutex...");
// after
let Some((_id, cmd)) = self.active_cmds.remove(&history_id) else {
return Err(DaemonError::Internal("active command vanished despite finalization mutex".into()));
}; Defensive patterns
Strategy: try-catch
Try / catch
// Treat as an internal bug; surface it instead of panicking:
let Some((_id, cmd)) = self.active_cmds.remove(&history_id) else {
tracing::error!("cancel: active_cmds entry missing for {history_id}");
return Err(DaemonError::Internal("lost active command".into()));
}; Prevention
- Audit every code path that mutates shared maps for consistent lock usage
- Prefer returning Result over expect for map lookups in concurrent code
- Run daemon tests under stress/loom-style concurrency checkers
When it happens
Trigger: Calling `cancel(history_id)` for a command whose active-cmd entry was concurrently removed by another path, despite the invariant stating removers must hold the finalization mutex first.
Common situations: A race between cancel and finish/cleanup paths in the daemon; a bug introduced by modifying one remover to bypass the mutex; duplicated cancel requests processed in parallel.
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
- issue in stats average query
- issue in stats exits query
- issue in stats day of week query
- issue in stats duration over time query
- the vector is not empty
AI-assisted analysis of atuinsh/atuin@c0c717ab04 (2026-09-12).
Data as JSON: /api/errors/1c73314462598fd6.
Report an issue: GitHub.