RightNow-AI/openfang · error

Failed to listen for SIGTERM

Error message

Failed to listen for SIGTERM

What it means

tokio's signal(SignalKind::terminate()) registers a handler for SIGTERM via the OS (signalfd/kevent). It returns Err when the process cannot register the signal handler — typically when running outside a proper process context (e.g. not a session leader, resource limits, or on platforms where signal registration is unsupported). The code panics with this expect message instead of returning the error.

Source

Thrown at crates/openfang-api/src/server.rs:957

fn restrict_permissions(_path: &Path) {}

/// Read daemon info from the standard location.
pub fn read_daemon_info(home_dir: &Path) -> Option<DaemonInfo> {
    let info_path = home_dir.join("daemon.json");
    let contents = std::fs::read_to_string(info_path).ok()?;
    serde_json::from_str(&contents).ok()
}

/// Wait for an OS termination signal OR an API shutdown request.
///
/// On Unix: listens for SIGINT, SIGTERM, and API notify.
/// On Windows: listens for Ctrl+C and API notify.
async fn shutdown_signal(api_shutdown: Arc<tokio::sync::Notify>) {
    #[cfg(unix)]
    {
        use tokio::signal::unix::{signal, SignalKind};
        let mut sigint = signal(SignalKind::interrupt()).expect("Failed to listen for SIGINT");
        let mut sigterm = signal(SignalKind::terminate()).expect("Failed to listen for SIGTERM");

        tokio::select! {
            _ = sigint.recv() => {
                info!("Received SIGINT (Ctrl+C), shutting down...");
            }
            _ = sigterm.recv() => {
                info!("Received SIGTERM, shutting down...");
            }
            _ = api_shutdown.notified() => {
                info!("Shutdown requested via API, shutting down...");
            }
        }
    }

    #[cfg(not(unix))]
    {
        tokio::select! {
            _ = tokio::signal::ctrl_c() => {

View on GitHub (pinned to acf2587e46)

Solutions

  1. Check container/sandbox security profile allows signalfd / signal handler registration (adjust seccomp or AppArmor profile).
  2. Check ulimit -i / RLIMIT_SIGPENDING; raise limits if exhausted.
  3. Replace .expect with graceful error propagation and fall back to Ctrl+C-only handling (SIGINT) or polling shutdown flags.
  4. Ensure tokio's 'signal' feature is enabled and a reactor is running before registering handlers.

Example fix

// before
let mut sigterm = signal(SignalKind::terminate()).expect("Failed to listen for SIGTERM");
// after
let mut sigterm = match signal(SignalKind::terminate()) {
    Ok(s) => s,
    Err(e) => {
        eprintln!("SIGTERM handling unavailable ({e}); only Ctrl+C will stop the daemon");
        return;
    }
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Unix: verify the process can register a SIGTERM handler before entering the daemon loop
#[cfg(unix)]
fn can_listen_sigterm() -> bool {
    use tokio::signal::unix::{signal, SignalKind};
    signal(SignalKind::terminate()).is_ok()
}

Try / catch

// return Result instead of expect
fn shutdown_signal(api_shutdown: Arc<tokio::sync::Notify>) -> Result<(), std::io::Error> {
    #[cfg(unix)]
    {
        use tokio::signal::unix::{signal, SignalKind};
        let mut sigterm = signal(SignalKind::terminate())?; // propagated, no panic
        // ...select! as before
    }
    Ok(())
}

Prevention

When it happens

Trigger: Calling shutdown_signal() on a Unix platform where tokio::signal::unix::signal(SignalKind::terminate()) fails: e.g. RLIMIT_SIGPENDING exhaustion, running in restricted sandbox/container (seccomp blocking signalfd), or on an unsupported Unix target.

Common situations: Running the daemon in a heavily restricted container (gVisor, minimal seccomp profiles), under PID/resource limit starvation, or on exotic Unix targets where tokio's signal driver cannot initialize.

Related errors


AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02). Data as JSON: /api/errors/541fe8f2160d3fe1. Report an issue: GitHub.