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
- Do nothing — the existing daemon is running; use it (this is usually expected behavior).
- Stop the existing daemon (`pkill atuin` or stop the systemd unit) if you genuinely want a fresh one.
- Check who holds the lock: `lsof <pidfile path>` / `fuser` on the pidfile path from the message.
- 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.
- 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
- Rely on autostart instead of manually launching daemons.
- Stop existing daemons/systemd units before starting new ones.
- Avoid sharing one HOME between concurrent atuin sessions.
- Check `lsof` on the pidfile path to find the lock holder.
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
- timed out waiting for lock at
- `atuin history tail` requires Atuin to be built with the…
- Atuin running with no parent!
- daemon autostart is incompatible with…
- Daemon rejected shutdown request
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)