{"record":{"id":"46ed456e95b58aa9","repo":"xai-org/grok-build","slug":"refusing-degenerate-process-group-id-pid-0-ow","errorCode":null,"errorMessage":"refusing degenerate process-group id {pid} (0 = own group, 1 = init)","messagePattern":"refusing degenerate process-group id (.+?) \\(0 = own group, 1 = init\\)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-tty-utils/src/lib.rs","lineNumber":617,"sourceCode":"/// a standing guarantee that `killpg` can only ever reach a real, foreign\n/// group — the highest-blast-radius primitive in process teardown is validated\n/// once, at enrollment, rather than re-checked at each call site.\n#[cfg(unix)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct ProcessGroupId(u32);\n\n#[cfg(unix)]\nimpl ProcessGroupId {\n    /// Validate a group-leader pid. Errors for pid `0` (the caller's own\n    /// group), pid `1` (init), or the caller's own process group (signalling it\n    /// would kill this very process). A child spawned into its own group\n    /// (`setpgid`/`setsid`, e.g. via [`new_process_group`] or a `detach_*`\n    /// helper) always has a leader pid `> 1` distinct from the caller's pgid, so\n    /// a well-formed enrollment never trips this — it only catches a child that\n    /// was never grouped, which would otherwise broadcast the kill.\n    pub fn new(pid: u32) -> io::Result<Self> {\n        if pid <= 1 {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\"refusing degenerate process-group id {pid} (0 = own group, 1 = init)\"),\n            ));\n        }\n        // killpg_unix casts `pid as i32`; values > i32::MAX wrap to negative,\n        // and killpg with a negative pgid returns EINVAL on Linux/macOS. Reject\n        // here so the invariant is safe-by-construction, not safe-by-OS-quirk.\n        if pid > i32::MAX as u32 {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\"process-group id {pid} exceeds i32::MAX; cannot be used with killpg\"),\n            ));\n        }\n        if i64::from(pid) == i64::from(nix::unistd::getpgrp().as_raw()) {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\"refusing to killpg the caller's own process group ({pid})\"),\n            ));","sourceCodeStart":599,"sourceCodeEnd":635,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-tty-utils/src/lib.rs#L599-L635","documentation":"ProcessGroup::new validates the pid it will later use with killpg. A process-group id of 0 means \"the caller's own group\" and 1 is init, so enrolling either would make a broadcast kill hit the wrong processes. The guard rejects pid <= 1 as InvalidInput so the invariant is safe by construction.","triggerScenarios":"Calling ProcessGroup::new(0) or ProcessGroup::new(1), typically when a spawn helper returned a placeholder/invalid pid or the child was never actually put into a new process group (no setpgid/setsid).","commonSituations":"Propagating a default/zero pid after a failed spawn; recording the pid of a child that never got grouped; mocking code returning pid 1.","solutions":["Fix the spawn path so the child is created with a new process group and its real leader pid (> 1) is captured","Reject or log pid 0/1 at the source where the pid is obtained instead of enrolling it","Use the library's new_process_group / detach_* helpers, which guarantee a valid leader pid"],"exampleFix":"// before\nlet pg = ProcessGroup::new(child_pid)?; // child_pid == 0 after failed spawn\n// after\nif child_pid <= 1 {\n    return Err(anyhow!(\"spawn failed: no valid process-group leader\"));\n}\nlet pg = ProcessGroup::new(child_pid)?;","handlingStrategy":"validation","validationCode":"fn enrollable_pid(pid: u32) -> bool { pid > 1 }\nif !enrollable_pid(child_pid) {\n    return Err(anyhow!(\"spawn did not produce a valid process-group leader\"));\n}","typeGuard":"fn enrollable_pid(pid: u32) -> bool { pid > 1 }","tryCatchPattern":"match ProcessGroup::new(pid) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidInput => {\n        log::error!(\"child was never put in a new process group (pid={pid})\");\n        return Err(e.into());\n    }\n    other => other?,\n}","preventionTips":["Always spawn children via setpgid/setsid helpers so the leader pid is real and > 1","Never default the pid to 0 when a spawn fails — surface the spawn error instead","Log a warning if the pid captured from the child is 0 or 1"],"tags":["process","unix","killpg","safety","rust"],"backgroundTag":"invalid-process-group-id","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}