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

  1. Retry try_load_or_generate once or twice — the race window is nanoseconds-wide and a retry almost always succeeds
  2. Identify and stop the concurrent deleter: check security software / sync clients that manage the data directory
  3. Pre-create the key file on one process (e.g. run `atuin key` or a single registration step) before starting parallel consumers
  4. 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

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


AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16). Data as JSON: /api/errors/fd9b63c3be6a281b. Report an issue: GitHub.