atuinsh/atuin · error · std::io::Error
key file vanished immediately after a concurrent write
Error message
key file vanished immediately after a concurrent write
What it means
Produced by Key::try_load_or_generate in atuin-common's PASETO V4 key handling. The sequence is: the key file did not exist (NoEntry), a fresh key was generated, try_write_path refused because a file appeared concurrently (AlreadyExists), and the follow-up try_load_from_path then found the file gone again (NoEntry). That double-disappearance is mapped to io::ErrorKind::NotFound with this message. It signals an extremely tight race where another process (or an external deleter) is creating and removing the key file between syscalls.
Source
Thrown at crates/atuin-common/src/encryption/paseto_v4.rs:278
/// [`Self::generate`], stores it and returns it.
pub fn try_load_or_generate(path: &Path) -> Result<Self, KeyFileLoadOrGenerateError> {
match Self::try_load_from_path(path) {
Ok(s) => Ok(s),
Err(KeyFileLoadingError::NoEntry) => {
let key = Self::generate();
match key.try_write_path(path) {
Ok(()) => Ok(key),
// We lost a race: another process wrote a key between our existence check and
// our write. Adopt whatever landed on disk rather than clobbering it or
// panicking.
Err(KeyFileStoringError::AlreadyExists) => Self::try_load_from_path(path)
.map_err(|e| match e {
KeyFileLoadingError::Io(io) => KeyFileLoadOrGenerateError::Io(io),
KeyFileLoadingError::Decoding(d) => {
KeyFileLoadOrGenerateError::Decoding(d)
}
KeyFileLoadingError::NoEntry => {
KeyFileLoadOrGenerateError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
"key file vanished immediately after a concurrent write",
))
}
}),
Err(KeyFileStoringError::Io(io)) => Err(io.into()),
}
}
Err(KeyFileLoadingError::Io(io)) => Err(io.into()),
Err(KeyFileLoadingError::Decoding(d)) => Err(d.into()),
}
}
/// Get the mnemonic of this particular key.
pub fn try_mnemonic(&self) -> Result<bip39::Mnemonic, bip39::ErrorKind> {
bip39::Mnemonic::from_entropy(self.as_bytes(), bip39::Language::English)
}
View on GitHub (pinned to 202f6ad98e)
Solutions
- Retry try_load_or_generate once or twice — the race window is nanoseconds-wide and a retry almost always succeeds
- Identify and stop the concurrent deleter: check security software / sync clients that manage the data directory
- Pre-create the key file on one process (e.g. run `atuin key` or a single registration step) before starting parallel consumers
- Verify the data directory is on a local filesystem, not a flaky network mount
Example fix
// before
let key = Key::try_load_or_generate(&path)?;
// after
let key = Key::try_load_or_generate(&path)
.or_else(|_| Key::try_load_or_generate(&path))?; Defensive patterns
Strategy: retry
Validate before calling
// Serialize first-run key creation across processes with a lockfile
let lock = fs4::FileExt::try_lock_exclusive(
&mut std::fs::File::create(path.with_extension("lock"))?,
)?; // hold while calling try_load_or_generate Try / catch
let mut attempts = 0;
let key = loop {
attempts += 1;
match Key::try_load_or_generate(&path) {
Ok(k) => break k,
Err(e) if attempts < 3 => continue, // race window is tiny; retry wins
Err(e) => return Err(e.into()),
}
}; Prevention
- Pre-create the key file once (single registration step) before starting many parallel Atuin processes
- Keep the Atuin data directory out of sync-tool managed folders (Dropbox/Syncthing) that may create/delete files
- Exclude the key file from antivirus/security scanning interference
- Use a lockfile around first-run key creation in custom tooling
When it happens
Trigger: Two Atuin processes on first run hitting try_load_or_generate for the same path at the same instant, combined with something deleting the file in between; a filesystem watcher, sync client, or antivirus quarantining the freshly created key file; a network filesystem with create/delete visibility lag.
Common situations: First-run races between the atuin daemon and the CLI in fresh environments; dotfile-sync tools (Dropbox, Syncthing) managing ~/.local/share/atuin and conflicting with key creation; security software removing unknown key files; tests that run many atuin processes in parallel against a shared HOME.
Related errors
- Empty theme directory override and could not find theme else
- Failed to deserialize theme: {}
- Parent requested but we hit the recursion limit
- frame exceeds maximum length
- eof in the middle of a frame
AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16).
Data as JSON: /api/errors/fd9b63c3be6a281b.
Report an issue: GitHub.