{"record":{"id":"86c5bd949bb6a216","repo":"jdx/mise","slug":"piped-stdout","errorCode":null,"errorMessage":"piped stdout","messagePattern":"piped stdout","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cmd/bounded.rs","lineNumber":27,"sourceCode":"    /// Capture a finite response, with one deadline for the process and pipes.\n    /// This command owns its child tree even when mise itself is nested.\n    pub(crate) async fn read_isolated(mut self, limit: usize) -> Result<String> {\n        let _read_lock = RAW_LOCK.read().await;\n        let timeout = self.timeout.unwrap_or(Duration::from_secs(5));\n        self.cmd.kill_on_drop(true);\n        self.cmd\n            .stdin(Stdio::null())\n            .stdout(Stdio::piped())\n            .stderr(Stdio::piped());\n        #[cfg(unix)]\n        {\n            self.cmd.env(TASK_PGID_MANAGED_ENV, \"1\");\n            self.cmd.process_group(0);\n        }\n        let mut child = self.spawn_async_with_etxtbsy_retry().await?;\n        let _running = RunningPidGuard::new(child.id());\n        let tree = ChildTree::new(&mut child)?;\n        let stdout = child.stdout.take().expect(\"piped stdout\");\n        let stderr = child.stderr.take().expect(\"piped stderr\");\n        // One budget for both pipes: judging them only once both reach EOF\n        // would let each hold the whole limit first, so a command could\n        // allocate twice what was asked before anyone objected.\n        let budget = AtomicUsize::new(limit);\n        let result = tokio::time::timeout(timeout, async {\n            tokio::try_join!(\n                child.wait(),\n                capture(stdout, &budget, limit),\n                capture(stderr, &budget, limit)\n            )\n        })\n        .await;\n        let (status, stdout, _stderr) = match result {\n            Ok(Ok(output)) => output,\n            Ok(Err(err)) => {\n                end(&mut child, tree).await;\n                return Err(err.into());","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/cmd/bounded.rs#L9-L45","documentation":"`read_isolated` (in src/cmd/bounded.rs) runs a command in its own process group with a bounded output budget, which requires both pipes. This `.expect(\"piped stdout\")` (and its stderr sibling) panics if the child was not spawned with piped stdio — a spawn-configuration invariant for the task-output capture path used by mise tasks.","triggerScenarios":"A task command spawned for `read_isolated` with inherited or null stdio; a wrapper (e.g. one setting process groups or env) that also overrides stdio; refactors in task spawn configuration that drop the pipes.","commonSituations":"Changes to `src/cmd/bounded.rs` or task spawn helpers that reconfigure stdio; custom task run modes (e.g. interactive tasks) accidentally routed through the bounded/isolated reader.","solutions":["Ensure the command sets `.stdout(Stdio::piped()).stderr(Stdio::piped())` before `spawn_async_with_etxtbsy_retry()`","Keep interactive (non-captured) task execution on a separate code path from `read_isolated`","Report with `MISE_DEBUG=1` and the failing task definition if seen on an unmodified build","Add a debug_assert or early error right after spawn verifying both pipes exist"],"exampleFix":"// before\nlet mut cmd = build_task_cmd(task); // stdio unset -> inherits\n// after\nlet mut cmd = build_task_cmd(task);\ncmd.stdout(Stdio::piped()).stderr(Stdio::piped());","handlingStrategy":"validation","validationCode":"// In task spawn helpers, before read_isolated:\nassert!(matches!(cmd.get_stdout(), Stdio::piped()) && matches!(cmd.get_stderr(), Stdio::piped()));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep interactive task modes on a separate execution path from bounded capture","Re-verify stdio after any wrapper that mutates process groups or env","Cover task capture with tests that assert output is fully captured"],"tags":["rust","panic","internal-invariant","process","stdio","tasks"],"backgroundTag":"internal-invariant-violation","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}