xai-org/grok-build · error · io::Error

process-group id {pid} exceeds i32::MAX; cannot be used with

Error message

process-group id {pid} exceeds i32::MAX; cannot be used with killpg

What it means

killpg takes a pid_t (i32); u32 pids above i32::MAX wrap to negative when cast, and killpg with a negative pgid fails with EINVAL (or signals a different group). ProcessGroup::new rejects such ids up front as InvalidInput so the invariant doesn't rely on OS quirks.

Source

Thrown at crates/codegen/xai-tty-utils/src/lib.rs:626

    /// Validate a group-leader pid. Errors for pid `0` (the caller's own
    /// group), pid `1` (init), or the caller's own process group (signalling it
    /// would kill this very process). A child spawned into its own group
    /// (`setpgid`/`setsid`, e.g. via [`new_process_group`] or a `detach_*`
    /// helper) always has a leader pid `> 1` distinct from the caller's pgid, so
    /// a well-formed enrollment never trips this — it only catches a child that
    /// 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
    }
}

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Reduce kernel.pid_max (e.g. echo 4194304 > /proc/sys/kernel/pid_max) on hosts that set extreme values
  2. Validate pid <= i32::MAX at the point the pid is captured from the OS/input
  3. Never enroll pids read from stale logs; re-resolve the live process instead

Example fix

// before
let pg = ProcessGroup::new(recorded_pid as u32)?;
// after
if recorded_pid > i32::MAX as u64 {
    return Err(anyhow!("stale pid {} exceeds i32::MAX", recorded_pid));
}
let pg = ProcessGroup::new(recorded_pid as u32)?;
Defensive patterns

Strategy: validation

Validate before calling

fn pid_fits_i32(pid: u32) -> bool { pid <= i32::MAX as u32 }
if !pid_fits_i32(pid) {
    return Err(anyhow!("pid {pid} cannot be used with killpg"));
}

Type guard

fn pid_fits_i32(pid: u32) -> bool { pid <= i32::MAX as u32 }

Try / catch

if let Err(e) = ProcessGroup::new(pid) {
    if e.kind() == io::ErrorKind::InvalidInput && pid > i32::MAX as u32 {
        log::error!("stale/oversized pid {pid}; re-resolve the live process");
    }
    return Err(e.into());
}

Prevention

When it happens

Trigger: Calling ProcessGroup::new with a pid greater than 2147483647 — possible on systems configured with very high pid_max, or from corrupt/stale bookkeeping data.

Common situations: Running on hosts with kernel.pid_max raised above 2^31 (rare); loading recorded pids from a journal/log and re-enrolling them; parsing pids from untrusted input as u64/u32.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/68ec966283d66fb6. Report an issue: GitHub.