{"record":{"id":"c5b66df5648797d3","repo":"atuinsh/atuin","slug":"failed-to-register-sigterm-handler","errorCode":null,"errorMessage":"failed to register sigterm handler","messagePattern":"failed to register sigterm handler","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin-daemon/src/lib.rs","lineNumber":120,"sourceCode":"        handle,\n    )\n    .await?;\n\n    // Run the daemon event loop\n    daemon.run_event_loop().await?;\n\n    // Stop all components on shutdown\n    daemon.stop_components().await;\n\n    tracing::info!(\"daemon shut down complete\");\n    Ok(())\n}\n\n/// Wait for a shutdown signal (Ctrl+C or SIGTERM).\n#[cfg(unix)]\nasync fn shutdown_signal() {\n    let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())\n        .expect(\"failed to register sigterm handler\");\n    let mut int = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())\n        .expect(\"failed to register sigint handler\");\n\n    tokio::select! {\n        _ = term.recv() => {},\n        _ = int.recv() => {},\n    }\n}\n\n/// Wait for a shutdown signal (Ctrl+C).\n#[cfg(not(unix))]\nasync fn shutdown_signal() {\n    tokio::signal::ctrl_c()\n        .await\n        .expect(\"failed to listen for ctrl+c\");\n}\n","sourceCodeStart":102,"sourceCodeEnd":137,"githubUrl":"https://github.com/atuinsh/atuin/blob/202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1/crates/atuin-daemon/src/lib.rs#L102-L137","documentation":"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.","triggerScenarios":"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.","commonSituations":"Embedding atuin-daemon's run path in a custom runtime (current_thread without enable_io); heavily loaded boxes at fd limits; restrictive seccomp profiles.","solutions":["Ensure the daemon runs inside a tokio runtime built with enable_all() — the shipped atuin binary does this","Raise fd/memory limits if registration fails at boot (ulimit -n, systemd LimitNOFILE)","When embedding, register signals inside the runtime context, never from a bare executor"],"exampleFix":"// before\nlet rt = tokio::runtime::Builder::new_multi_thread().build()?;\n\n// after — signal handlers need the IO driver\nlet rt = tokio::runtime::Builder::new_multi_thread()\n    .enable_all()\n    .build()?;","handlingStrategy":"validation","validationCode":"let rt = tokio::runtime::Builder::new_multi_thread()\n    .enable_all() // installs the IO/signal driver signal() requires\n    .build()?;\nrt.block_on(async {\n    // daemon code, including shutdown_signal()\n});","typeGuard":null,"tryCatchPattern":"match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {\n    Ok(term) => tokio::select! { _ = term.recv() => {} },\n    Err(e) => {\n        tracing::error!(\"sigterm handler unavailable: {e}; relying on SIGINT\");\n        tokio::signal::ctrl_c().await.ok();\n    }\n}","preventionTips":["Build every runtime that awaits signal APIs with enable_all()","Register exactly one shutdown-signal listener per process","Test daemon boot under the production container's fd and memory limits"],"tags":["rust","tokio","signals","sigterm","daemon","panic"],"backgroundTag":"tokio-signal-registration-failed","analyzedSha":"202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1","analyzedAt":"2026-08-16T19:30:24.731Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}