atuinsh/atuin · error

{message}

Error message

{message}

What it means

Before using a running daemon, the CLI checks that the daemon's reported version and protocol are compatible with the client (`ensure_reply_compatible`). On mismatch it builds a descriptive message: if autostart is enabled the daemon can simply be restarted transparently, so it bails with the plain message (the caller `try_with_restart` will handle restart); otherwise it appends guidance to enable autostart or restart manually.

Solutions

  1. Kill the running daemon (`pkill atuin` / stop the systemd unit) and let the new binary start it.
  2. Enable `daemon.autostart = true` in config so the CLI restarts mismatched daemons automatically.
  3. Re-run the command after restarting so both sides match versions.
  4. Ensure only one installation of atuin is on PATH (`which -a atuin`) and upgrade all of them consistently.

Example fix

# before
atuin stats  # daemon is old version -> version/protocol mismatch bail
# after
pkill atuin && atuin stats   # restart daemon with the upgraded client
Defensive patterns

Strategy: retry

Validate before calling

# compare running daemon version with the CLI
atuin version 2>/dev/null; pgrep -a atuin   # stale daemon process?

Try / catch

match client_cmd() {
    Err(e) if e.to_string().contains("Enable `daemon.autostart`") || e.to_string().contains("protocol") => {
        stop_daemon();
        client_cmd()  // retry; autostart spawns a matching daemon
    }
    other => other,
}

Prevention

When it happens

Trigger: Client and daemon binaries of different atuin versions are mixed (e.g. after upgrading the CLI but the old daemon process is still running), or the daemon speaks a different protocol version than the client expects.

Common situations: Upgrading atuin (package manager, cargo install) while a long-lived daemon keeps running the old build; switching between system and self-built binaries; running the CLI in a container against a host daemon.

Related errors


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

Appendix: source

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

    let _ = wait_until_ready(settings, timeout).await?;

    drop(startup_lock);
    Ok(())
}

async fn restart_daemon(settings: &Settings) -> Result<HistoryClient> {
    ensure_daemon_running(settings).await?;
    connect_client(settings).await
}

fn ensure_reply_compatible(settings: &Settings, version: &str, protocol: u32) -> Result<()> {
    if daemon_matches_expected(version, protocol) {
        return Ok(());
    }

    let message = daemon_mismatch_message(version, protocol);
    if settings.daemon.autostart {
        bail!("{message}");
    }

    bail!("{message}. Enable `daemon.autostart = true` or restart the daemon manually");
}

/// Acquire a [`HistoryClient`] connected to a daemon we expect to understand us, probing over the
/// wire-stable Status RPC first and restarting the daemon if it is absent or version/protocol
/// skewed.
///
/// TODO(markovejnovic): This is egregious slop, but not worse than the original solution. I want to
///                      remove this in a future PR: <https://github.com/atuinsh/atuin/pull/4002>
pub async fn ready_client(settings: &Settings) -> Result<HistoryClient> {
    match probe(settings).await {
        Probe::Ready(client) => return Ok(client),
        Probe::NeedsRestart(reason) if !settings.daemon.autostart => {
            bail!("{reason}. Enable `daemon.autostart = true` or restart the daemon manually");
        }
        Probe::Unreachable(err) if is_legacy_daemon_error(&err) => {

View on GitHub (pinned to 900007c480)