atuinsh/atuin · error

daemon already running

Error message

daemon already running (pidfile lock busy at {})

What it means

The daemon uses an advisory flock on its pidfile to guarantee a single instance per home. `PidFile::acquire` calls `try_lock`; if the lock is already held by another process (`WouldBlock`), it concludes another daemon is already running and bails instead of starting a second one.

Solutions

  1. Do nothing — the existing daemon is running; use it (this is usually expected behavior).
  2. Stop the existing daemon (`pkill atuin` or stop the systemd unit) if you genuinely want a fresh one.
  3. Check who holds the lock: `lsof <pidfile path>` / `fuser` on the pidfile path from the message.
  4. If a stale process is gone but the file remains, the flock is released automatically on process exit — verify no process, then remove the stale pidfile if needed.
  5. Use a different `ATUIN_HOME`/daemon socket path for isolated concurrent instances.

Example fix

# before
temuin daemon &  # second instance
atuin daemon      # -> daemon already running (pidfile lock busy ...)
# after
atuin daemon      # single instance; rely on autostart for on-demand start
Defensive patterns

Strategy: try-catch

Validate before calling

# ensure no daemon is already running before starting one
pgrep -x atuin && echo "daemon already running" || atuin daemon

Try / catch

match atuin::daemon::start() {
    Err(e) if e.to_string().contains("daemon already running") => {
        // expected: reuse the existing daemon
    }
    Err(e) => return Err(e),
    Ok(_) => {}
}

Prevention

When it happens

Trigger: Starting `atuin daemon` (or a command with autostart) while another atuin daemon process still holds the pidfile lock — e.g. an already-running daemon, a stale-but-live daemon from a previous session, or two daemons started concurrently.

Common situations: Double-starting the daemon via systemd socket + manual start; leftover daemon after a crashed shell without cleanup; running atuin under two different user sessions sharing a home directory; tests racing on the same HOME.

Related errors


AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19). Data as JSON: /api/errors/89afabce7b80b742. Report an issue: GitHub.

Appendix: source

Thrown at crates/atuin/src/command/client/daemon.rs:119

const DAEMON_VERSION: &str = env!("CARGO_PKG_VERSION");
const DAEMON_PROTOCOL_VERSION: u32 = 3;
const STARTUP_POLL: Duration = Duration::from_millis(40);
const LOCK_POLL: Duration = Duration::from_millis(20);
const LEGACY_DAEMON_RESTART_MESSAGE: &str = "legacy daemon detected; restart daemon manually";

struct PidfileGuard {
    file: File,
}

impl PidfileGuard {
    fn acquire(path: &Path) -> Result<Self> {
        let mut file = open_lock_file(path)?;

        match file.try_lock() {
            Ok(()) => {}
            Err(TryLockError::WouldBlock) => {
                bail!("daemon already running (pidfile lock busy at {})", path.display())
            }
            Err(TryLockError::Error(err)) => {
                return Err(err)
                    .wrap_err_with(|| format!("could not lock daemon pidfile {}", path.display()));
            }
        }

        file.set_len(0)
            .wrap_err_with(|| format!("could not truncate daemon pidfile {}", path.display()))?;
        writeln!(file, "{}", std::process::id())
            .and_then(|()| writeln!(file, "{DAEMON_VERSION}"))
            .wrap_err_with(|| format!("could not write daemon pidfile {}", path.display()))?;

        Ok(Self { file })
    }
}

impl Drop for PidfileGuard {

View on GitHub (pinned to 900007c480)