googleworkspace/cli · error
failed to listen for SIGINT
Error message
failed to listen for SIGINT
What it means
In shutdown_signal() (helpers/mod.rs) the SIGINT future's Result is unwrapped with .expect inside a spawned background task. tokio::signal::ctrl_c() returns Err only when the OS-level handler for SIGINT cannot be installed. The expect therefore panics inside the helper task; the Notify is never fired, so any long-running helper loop (gws gmail watch, gws events subscribe) that awaits shutdown_signal() hangs instead of exiting, and the panic may abort the process under panic=abort builds.
Source
Thrown at crates/google-workspace-cli/src/helpers/mod.rs:63
/// loop iterations.
pub(crate) async fn shutdown_signal() {
use std::sync::OnceLock;
use tokio::sync::Notify;
static NOTIFY: OnceLock<std::sync::Arc<Notify>> = OnceLock::new();
let notify = NOTIFY.get_or_init(|| {
let n = std::sync::Arc::new(Notify::new());
let n2 = n.clone();
tokio::spawn(async move {
#[cfg(unix)]
{
use tokio::signal::unix::{signal, SignalKind};
match signal(SignalKind::terminate()) {
Ok(mut sigterm) => {
tokio::select! {
res = tokio::signal::ctrl_c() => {
res.expect("failed to listen for SIGINT");
}
Some(_) = sigterm.recv() => {}
}
}
Err(e) => {
eprintln!(
"warning: could not register SIGTERM handler: {e}. \
Listening for Ctrl+C only."
);
tokio::signal::ctrl_c()
.await
.expect("failed to listen for SIGINT");
}
}
}
#[cfg(not(unix))]
{
tokio::signal::ctrl_c()View on GitHub (pinned to a3768d0e82)
Solutions
- Ensure the parent process does not ignore SIGINT: avoid nohup-style inheritance, or reset the disposition to default at startup (unsafe { libc::signal(libc::SIGINT, libc::SIG_DFL) }) before entering the async runtime
- In containers/sandboxes, allow the rt_sigaction syscall and use a standard init (docker --init, tini) so signal dispositions are sane
- As a maintainer, replace .expect with a graceful branch: log a warning on Err and still call n2.notify_waiters() (or exit the process) so the CLI cannot hang; today the panic in the detached task leaves notify.notified().await pending forever
- If hitting it operationally, kill the hung process with SIGKILL since neither SIGINT nor the shutdown path will complete once the listener task is dead
Example fix
// before
res = tokio::signal::ctrl_c() => {
res.expect("failed to listen for SIGINT");
}
// after
res = tokio::signal::ctrl_c() => {
if let Err(e) = res {
eprintln!("warning: SIGINT listener unavailable: {e}");
n2.notify_waiters(); // let shutdown_signal() return instead of hanging
}
} Defensive patterns
Strategy: validation
Validate before calling
// Reset an inherited SIG_IGN disposition before entering the tokio runtime,
// so ctrl_c()/SIGTERM registration cannot fail later.
#[cfg(unix)]
fn ensure_default_signal_dispositions() {
unsafe {
libc::signal(libc::SIGINT, libc::SIG_DFL);
libc::signal(libc::SIGTERM, libc::SIG_DFL);
}
}
fn main() {
#[cfg(unix)]
ensure_default_signal_dispositions();
// then build the runtime and run the CLI
} Try / catch
// shutdown_signal() itself cannot error; guard the wrapper so a dead
// listener task cannot hang the loop forever:
tokio::select! {
_ = shutdown_signal() => { /* graceful exit */ }
_ = tokio::time::sleep(std::time::Duration::from_secs(max_runtime)) => {
eprintln!("shutdown signal never arrived; exiting on timeout");
}
} Prevention
- Do not launch gws long-running helpers (gmail watch, events subscribe) from wrappers that ignore SIGINT (nohup-style inheritance); prefer exec so dispositions stay default
- In containers, permit signal-setup syscalls in the seccomp profile and use an init process (--init/tini)
- Until the expect is replaced upstream, pair shutdown_signal() with a timeout or external supervisor stop so a panicked listener task cannot wedge the process
When it happens
Trigger: Running under a supervisor that starts the process with SIGINT set to SIG_IGN (nohup, some init systems, docker-run --init edge cases, CI runners) — tokio/signal-hook refuses to install a handler over an inherited ignore disposition; a seccomp/container profile blocking the rt_sigaction syscall so handler registration fails; exotic platforms where signal(7) registration is unavailable.
Common situations: gws gmail watch launched via nohup or inside a wrapper script that ignores SIGINT; deployment under a process manager documented to inherit ignore dispositions; sandboxed CI executors with syscall filters; minimal containers that break signal setup.
Related errors
AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16).
Data as JSON: /api/errors/1b95bed5644f534c.
Report an issue: GitHub.