xai-org/grok-build · error · io::Error
refusing to killpg the caller's own process group ({pid})
Error message
refusing to killpg the caller's own process group ({pid}) What it means
Even when the pid is otherwise valid, ProcessGroup::new refuses a pid equal to the caller's current process group (getpgrp), because killpg on your own group would kill the caller itself. This is a self-preservation guard, reported as InvalidInput.
Source
Thrown at crates/codegen/xai-tty-utils/src/lib.rs:632
/// was never grouped, which would otherwise broadcast the kill.
pub fn new(pid: u32) -> io::Result<Self> {
if pid <= 1 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("refusing degenerate process-group id {pid} (0 = own group, 1 = init)"),
));
}
// killpg_unix casts `pid as i32`; values > i32::MAX wrap to negative,
// and killpg with a negative pgid returns EINVAL on Linux/macOS. Reject
// here so the invariant is safe-by-construction, not safe-by-OS-quirk.
if pid > i32::MAX as u32 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("process-group id {pid} exceeds i32::MAX; cannot be used with killpg"),
));
}
if i64::from(pid) == i64::from(nix::unistd::getpgrp().as_raw()) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("refusing to killpg the caller's own process group ({pid})"),
));
}
Ok(Self(pid))
}
/// The validated raw process-group id.
pub fn get(self) -> u32 {
self.0
}
}
/// Process-tree teardown handle.
///
/// - Unix: holds the validated group-leader id ([`ProcessGroupId`]); dispatches
/// to `killpg(pgid, signal)`.
/// - Windows: holds a Job Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`.View on GitHub (pinned to bc7f02eddd)
Solutions
- Only enroll pids of children spawned into a dedicated process group (verify with the new_process_group / detach_* helpers)
- Check the child actually left the caller's group: its pid must differ from the caller's pgid
- If you have the caller's own pid, skip enrollment entirely rather than wrapping it
Example fix
// before let pg = ProcessGroup::new(std::process::id())?; // refuses: own group // after let child = spawn_detached(cmd)?; // child runs in its own process group let pg = ProcessGroup::new(child.id())?;
Defensive patterns
Strategy: validation
Validate before calling
fn not_own_group(pid: u32) -> bool {
i64::from(pid) != i64::from(nix::unistd::getpgrp().as_raw())
}
assert!(not_own_group(child_pid), "cannot enroll caller's own process group"); Type guard
fn not_own_group(pid: u32) -> bool {
i64::from(pid) != i64::from(nix::unistd::getpgrp().as_raw())
} Try / catch
if let Err(e) = ProcessGroup::new(pid) {
if e.kind() == io::ErrorKind::InvalidInput {
log::error!("pid {pid} is the caller's own process group; skipping kill enrollment");
return Ok(()); // nothing to clean up
}
return Err(e.into());
} Prevention
- Only enroll children spawned into a dedicated process group via detach/new_process_group helpers
- Never pass std::process::id() (the caller's own pid) to enrollment
- After a failed spawn, verify the child actually left the caller's group before enrolling
When it happens
Trigger: Enrolling the caller's own pid or the pid of a process already sharing the caller's process group — e.g. passing std::process::id(), or a child that never actually moved to a new group (setpgid not called/failed).
Common situations: Calling the API from the top-level process instead of from the parent of a detached child; a spawn helper that silently failed to create the new group; testing with the current process's pid.
Related errors
- refusing degenerate process-group id {pid} (0 = own group, 1
- process-group id {pid} exceeds i32::MAX; cannot be used with
- git {shown} failed in {}: {}
- failed to exec {program}: {err}
- hook JSON alias validation failed: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/71824a3ebf023460.
Report an issue: GitHub.