{"record":{"id":"541fe8f2160d3fe1","repo":"RightNow-AI/openfang","slug":"failed-to-listen-for-sigterm","errorCode":null,"errorMessage":"Failed to listen for SIGTERM","messagePattern":"Failed to listen for SIGTERM","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/openfang-api/src/server.rs","lineNumber":957,"sourceCode":"fn restrict_permissions(_path: &Path) {}\n\n/// Read daemon info from the standard location.\npub fn read_daemon_info(home_dir: &Path) -> Option<DaemonInfo> {\n    let info_path = home_dir.join(\"daemon.json\");\n    let contents = std::fs::read_to_string(info_path).ok()?;\n    serde_json::from_str(&contents).ok()\n}\n\n/// Wait for an OS termination signal OR an API shutdown request.\n///\n/// On Unix: listens for SIGINT, SIGTERM, and API notify.\n/// On Windows: listens for Ctrl+C and API notify.\nasync fn shutdown_signal(api_shutdown: Arc<tokio::sync::Notify>) {\n    #[cfg(unix)]\n    {\n        use tokio::signal::unix::{signal, SignalKind};\n        let mut sigint = signal(SignalKind::interrupt()).expect(\"Failed to listen for SIGINT\");\n        let mut sigterm = signal(SignalKind::terminate()).expect(\"Failed to listen for SIGTERM\");\n\n        tokio::select! {\n            _ = sigint.recv() => {\n                info!(\"Received SIGINT (Ctrl+C), shutting down...\");\n            }\n            _ = sigterm.recv() => {\n                info!(\"Received SIGTERM, shutting down...\");\n            }\n            _ = api_shutdown.notified() => {\n                info!(\"Shutdown requested via API, shutting down...\");\n            }\n        }\n    }\n\n    #[cfg(not(unix))]\n    {\n        tokio::select! {\n            _ = tokio::signal::ctrl_c() => {","sourceCodeStart":939,"sourceCodeEnd":975,"githubUrl":"https://github.com/RightNow-AI/openfang/blob/acf2587e46be174c10200489c9a2d23a39a98aeb/crates/openfang-api/src/server.rs#L939-L975","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check container/sandbox security profile allows signalfd / signal handler registration (adjust seccomp or AppArmor profile).","Check ulimit -i / RLIMIT_SIGPENDING; raise limits if exhausted.","Replace .expect with graceful error propagation and fall back to Ctrl+C-only handling (SIGINT) or polling shutdown flags.","Ensure tokio's 'signal' feature is enabled and a reactor is running before registering handlers."],"exampleFix":"// before\nlet mut sigterm = signal(SignalKind::terminate()).expect(\"Failed to listen for SIGTERM\");\n// after\nlet mut sigterm = match signal(SignalKind::terminate()) {\n    Ok(s) => s,\n    Err(e) => {\n        eprintln!(\"SIGTERM handling unavailable ({e}); only Ctrl+C will stop the daemon\");\n        return;\n    }\n};","handlingStrategy":"try-catch","validationCode":"// Unix: verify the process can register a SIGTERM handler before entering the daemon loop\n#[cfg(unix)]\nfn can_listen_sigterm() -> bool {\n    use tokio::signal::unix::{signal, SignalKind};\n    signal(SignalKind::terminate()).is_ok()\n}","typeGuard":null,"tryCatchPattern":"// return Result instead of expect\nfn shutdown_signal(api_shutdown: Arc<tokio::sync::Notify>) -> Result<(), std::io::Error> {\n    #[cfg(unix)]\n    {\n        use tokio::signal::unix::{signal, SignalKind};\n        let mut sigterm = signal(SignalKind::terminate())?; // propagated, no panic\n        // ...select! as before\n    }\n    Ok(())\n}","preventionTips":["Never .expect on signal registration in daemon code; propagate or degrade gracefully.","Test daemon startup inside your actual container/sandbox security profile.","Raise RLIMIT_SIGPENDING on hosts running many processes.","Keep tokio 'signal' feature enabled and register handlers after runtime init."],"tags":["tokio","signals","unix","panic","daemon"],"backgroundTag":"signal-handler-registration-failed","analyzedSha":"acf2587e46be174c10200489c9a2d23a39a98aeb","analyzedAt":"2026-09-02T22:42:28.464Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}