{"record":{"id":"ce0d53cbb140475c","repo":"neondatabase/neon","slug":"failed-to-send-signal-to-process-name-with-pid","errorCode":null,"errorMessage":"Failed to send signal to {process_name} with pid {pid}: {e}","messagePattern":"Failed to send signal to (.+?) with pid (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"control_plane/src/background_process.rs","lineNumber":213,"sourceCode":"    // send signal\n    let sig = if immediate {\n        print!(\"Stopping {process_name} with pid {pid} immediately..\");\n        Signal::SIGQUIT\n    } else {\n        print!(\"Stopping {process_name} with pid {pid} gracefully..\");\n        Signal::SIGTERM\n    };\n    io::stdout().flush().unwrap();\n    match kill(pid, sig) {\n        Ok(()) => (),\n        Err(Errno::ESRCH) => {\n            // Again, don't delete the pid file. The unlink can race with a new pid file being created.\n            println!(\n                \"{process_name} with pid {pid} does not exist, but a pid file {pid_file:?} was found. Likely the pid got recycled. Lucky we didn't harm anyone.\"\n            );\n            return Ok(());\n        }\n        Err(e) => anyhow::bail!(\"Failed to send signal to {process_name} with pid {pid}: {e}\"),\n    }\n\n    // Wait until process is gone\n    wait_until_stopped(process_name, pid)?;\n    Ok(())\n}\n\npub fn wait_until_stopped(process_name: &str, pid: Pid) -> anyhow::Result<()> {\n    for retries in 0..STOP_RETRIES {\n        match process_has_stopped(pid) {\n            Ok(true) => {\n                println!(\"\\n{process_name} stopped\");\n                return Ok(());\n            }\n            Ok(false) => {\n                if retries == NOTICE_AFTER_RETRIES {\n                    // The process is taking a long time to start up. Keep waiting, but\n                    // print a message","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/control_plane/src/background_process.rs#L195-L231","documentation":"stop_process resolves the pid from the pid file and sends SIGTERM (or SIGKILL for immediate mode) with nix::sys::signal::kill. ESRCH (process gone) is handled gracefully, but any other errno is fatal: in practice almost always EPERM, meaning the current user lacks permission to signal that pid. The pid file is deliberately left in place on this path.","triggerScenarios":"Calling stop_process when the target process (pageserver/safekeeper/compute) was started by a different user (root vs your user), or in a container where the process runs under a different UID. Rarely EINVAL from an invalid signal number after code changes.","commonSituations":"Mixing sudo-launched and user-launched processes in one neon_local env; running `neon_local start` as root once and later stopping as a normal user (or vice versa); SELinux/AppArmor denying signals; leftover processes owned by another user after a CI job ran as a different account.","solutions":["Identify the owner: `ps -o user= -p <pid>` and align by stopping with the same user that started it (`sudo neon_local env stop` or the equivalent).","Kill the process directly as the right user: `sudo kill <pid>` (SIGTERM first, then SIGKILL), then re-run stop or remove the stale pid file once dead.","Audit your workflow so start and stop always run under the same account (never mix sudo and non-sudo for one env).","If this recurs in CI, stop processes in the same container/shell context that started them."],"exampleFix":"// before\nstop_process(immediate, process_name, &pid_file)?; // EPERM: Failed to send signal\n\n// after\nmatch stop_process(immediate, process_name, &pid_file) {\n    Err(e) if e.to_string().contains(\"Failed to send signal\") => {\n        let pid = pid_file::read(&pid_file)?.unwrap_pid();\n        std::process::Command::new(\"sudo\")\n            .args([\"kill\", &pid.to_string()])\n            .status()?;\n    }\n    other => other?,\n}","handlingStrategy":"try-catch","validationCode":"// verify you can signal the pid before attempting a graceful stop\nlet pid = match pid_file::read(&pid_file)? { pid_file::PidFileRead::LockedByOtherProcess(p) => p, _ => return Ok(()) };\nmatch kill(pid, None) {\n    Ok(_) => (),\n    Err(nix::errno::Errno::EPERM) => /* need elevated privileges */,\n    Err(nix::errno::Errno::ESRCH) => return Ok(()),\n    Err(e) => return Err(e.into()),\n}","typeGuard":null,"tryCatchPattern":"match stop_process(immediate, process_name, &pid_file) {\n    Err(e) if e.to_string().contains(\"Failed to send signal\") => {\n        // cross-privilege stop: re-signal via sudo, then retry\n        std::process::Command::new(\"sudo\").args([\"kill\", \"--\", &pid.to_string()]).status()?;\n        stop_process(immediate, process_name, &pid_file)\n    }\n    other => other,\n}","preventionTips":["Start and stop all processes in an env as the same user; never mix sudo and non-sudo.","Check `ps -o user= -p <pid>` when a stop mysteriously fails with EPERM.","In CI, run setup and teardown in the same container/user context."],"tags":["rust","neon","control-plane","background-process","signal","permissions","eperm","process-management"],"backgroundTag":"kill-permission-denied","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}