neondatabase/neon · error
Failed to send signal to process with pid {pid}: {err}
Error message
Failed to send signal to process with pid {pid}: {err} What it means
process_has_stopped probes liveness with signal 0 via kill(pid, None): Ok means still alive, ESRCH means gone, and any other errno raises this error. In practice it is EPERM — the polling user lacks permission to signal the pid — which makes the stop loop abort instead of continuing to wait. It is the read-only twin of the stop_process signal error.
Source
Thrown at control_plane/src/background_process.rs:395
{
match status_check().await {
Ok(true) => match pid_file::read(pid_file_to_check)? {
PidFileRead::NotExist => Ok(false),
PidFileRead::LockedByOtherProcess(pid_in_file) => Ok(pid_in_file == pid),
PidFileRead::NotHeldByAnyProcess(_) => Ok(false),
},
Ok(false) => Ok(false),
Err(e) => anyhow::bail!("process failed to start: {e}"),
}
}
pub(crate) fn process_has_stopped(pid: Pid) -> anyhow::Result<bool> {
match kill(pid, None) {
// Process exists, keep waiting
Ok(_) => Ok(false),
// Process not found, we're done
Err(Errno::ESRCH) => Ok(true),
Err(err) => anyhow::bail!("Failed to send signal to process with pid {pid}: {err}"),
}
}
View on GitHub (pinned to 8f60b04da4)
Solutions
- Check ownership: `ps -o user= -p <pid>`; stop the process as the user that started it (e.g. prefix the stop command with sudo).
- Kill the leftover process directly as the correct user (`sudo kill <pid>`), then let stop_process see ESRCH and finish cleanly.
- Standardize one account for starting and stopping all processes in a neon_local env.
- If the pid is long gone but the error persists, verify with `kill -0 <pid>` from the same shell to reproduce the permission denial.
Example fix
// before
wait_until_stopped(process_name, pid)?; // EPERM on signal-0 probe
// after
// probe with the same privileges used to start the process
let alive = std::process::Command::new("sudo")
.args(["kill", "-0", &pid.to_string()])
.status()?.success();
if !alive { /* stopped */ } Defensive patterns
Strategy: try-catch
Validate before calling
// before waiting, confirm signal permission with a signal-0 probe
match kill(pid, None) {
Err(nix::errno::Errno::EPERM) => anyhow::bail!("no permission to probe pid {pid}; stop as the owning user"),
_ => {},
} Try / catch
match wait_until_stopped(process_name, pid) {
Err(e) if e.to_string().contains("Failed to send signal to process") => {
// permission problem, not a hang: switch privileges and re-probe
std::process::Command::new("sudo").args(["kill", "-0", &pid.to_string()]).status()?;
Ok(())
}
other => other,
} Prevention
- Run stop flows under the same user that started the processes.
- Treat EPERM on signal-0 probes as a privilege mismatch, not a liveness signal.
- Automate env teardown in the same account/context used for setup.
When it happens
Trigger: Calling stop_process/wait_until_stopped when the target pid belongs to a different UID than the caller (process started under root, stopped under a normal user or vice versa). Also possible on systems with LSM policies denying signal-0 probes.
Common situations: Mixed-privilege workflows: one `neon_local` invocation under sudo, a later stop without it; CI containers where the compute process runs as another user; reusing an env directory across different accounts.
Related errors
- Failed to send signal to {process_name} with pid {pid}: {e}
- `datadir` must be a directory when calling this function: {d
- {} did not start+pass status checks within {:?} seconds
- {} with pid {} did not stop in {:?} seconds
- process failed to start: {e}
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/72da576ee2ce41d8.
Report an issue: GitHub.