atuinsh/atuin · error

failed to register sigterm handler

Error message

failed to register sigterm handler

What it means

At startup, atuin-daemon registers a SIGTERM listener with tokio::signal::unix::signal(SignalKind::terminate()) inside shutdown_signal(). Registration fails when the current tokio runtime has no IO/signal driver enabled, or when OS-level setup fails (e.g. resource exhaustion). The expect converts registration failure into an immediate daemon panic.

Source

Thrown at crates/atuin-daemon/src/lib.rs:120

        handle,
    )
    .await?;

    // Run the daemon event loop
    daemon.run_event_loop().await?;

    // Stop all components on shutdown
    daemon.stop_components().await;

    tracing::info!("daemon shut down complete");
    Ok(())
}

/// Wait for a shutdown signal (Ctrl+C or SIGTERM).
#[cfg(unix)]
async fn shutdown_signal() {
    let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
        .expect("failed to register sigterm handler");
    let mut int = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
        .expect("failed to register sigint handler");

    tokio::select! {
        _ = term.recv() => {},
        _ = int.recv() => {},
    }
}

/// Wait for a shutdown signal (Ctrl+C).
#[cfg(not(unix))]
async fn shutdown_signal() {
    tokio::signal::ctrl_c()
        .await
        .expect("failed to listen for ctrl+c");
}

View on GitHub (pinned to 202f6ad98e)

Solutions

  1. Ensure the daemon runs inside a tokio runtime built with enable_all() — the shipped atuin binary does this
  2. Raise fd/memory limits if registration fails at boot (ulimit -n, systemd LimitNOFILE)
  3. When embedding, register signals inside the runtime context, never from a bare executor

Example fix

// before
let rt = tokio::runtime::Builder::new_multi_thread().build()?;

// after — signal handlers need the IO driver
let rt = tokio::runtime::Builder::new_multi_thread()
    .enable_all()
    .build()?;
Defensive patterns

Strategy: validation

Validate before calling

let rt = tokio::runtime::Builder::new_multi_thread()
    .enable_all() // installs the IO/signal driver signal() requires
    .build()?;
rt.block_on(async {
    // daemon code, including shutdown_signal()
});

Try / catch

match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
    Ok(term) => tokio::select! { _ = term.recv() => {} },
    Err(e) => {
        tracing::error!("sigterm handler unavailable: {e}; relying on SIGINT");
        tokio::signal::ctrl_c().await.ok();
    }
}

Prevention

When it happens

Trigger: Reaching daemon shutdown_signal() inside a tokio runtime built without .enable_all()/.enable_io(); hitting EMFILE or memory limits while tokio installs its signal machinery; a sandbox policy blocking sigaction/eventfd setup.

Common situations: Embedding atuin-daemon's run path in a custom runtime (current_thread without enable_io); heavily loaded boxes at fd limits; restrictive seccomp profiles.

Related errors


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