{"record":{"id":"6ca0e43045613973","repo":"jdx/mise","slug":"piped","errorCode":null,"errorMessage":"piped","messagePattern":"piped","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/describe_command.rs","lineNumber":146,"sourceCode":"        use std::os::unix::process::CommandExt;\n        // its own process group: a timeout ends what the shell started too\n        shell.process_group(0);\n    }\n    #[cfg(windows)]\n    let (mut child, job) = windows_job::spawn(&mut shell)?;\n    #[cfg(not(windows))]\n    let mut child = shell.spawn()?;\n    let active = ActiveCommand(RunningCommand {\n        pid: child.id(),\n        #[cfg(windows)]\n        job: std::sync::Arc::new(job),\n    });\n    if let Ok(mut running) = RUNNING.lock() {\n        *running = Some(active.0.clone());\n    }\n    // stdin is written and closed on its own thread: a command that answers\n    // before reading everything must not block us\n    let mut stdin = child.stdin.take().expect(\"piped\");\n    std::thread::spawn(move || {\n        let _ = stdin.write_all(&input);\n    });\n    let stdout = child.stdout.take().expect(\"piped\");\n    let (sender, receiver) = std::sync::mpsc::channel();\n    std::thread::spawn(move || {\n        let mut out = Vec::new();\n        let _ = stdout.take(DIFF_LIMIT as u64 + 1).read_to_end(&mut out);\n        let _ = sender.send(out);\n    });\n    let started = Instant::now();\n    let status = loop {\n        if let Some(status) = child.try_wait()? {\n            break status;\n        }\n        if started.elapsed() >= timeout {\n            // the shell and whatever it started; the reader thread ends\n            // with the last writer of the pipe, so it is not waited for","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/describe_command.rs#L128-L164","documentation":"run_with_limits spawns a child with stdin piped, then takes the StdinChildPipe with expect(\"piped\"). Because the child was created with Stdio::piped() for stdin, this can only fail if the internal construction changed — the expect documents an invariant rather than an expected runtime failure.","triggerScenarios":"Calling describe_command's run/run_with_limits when the child process creation code no longer configures stdin as piped — i.e. only after a code change breaks the invariant; not triggerable from user input.","commonSituations":"Developers modifying describe_command.rs and changing the Command's stdio configuration; reviewers seeing this panic in CI after such a change.","solutions":["Restore Stdio::piped() for stdin when spawning the child in run_with_limits","Use match/expect with a descriptive message and handle None gracefully if piped stdin becomes conditional"],"exampleFix":"// before\nlet mut stdin = child.stdin.take().expect(\"piped\");\n// after\nlet Some(mut stdin) = child.stdin.take() else {\n    return Err(anyhow!(\"child stdin was not piped\"));\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// if contributing upstream, prefer explicit handling over expect\nlet Some(mut stdin) = child.stdin.take() else {\n    return Err(anyhow!(\"stdin not piped\"));\n};","preventionTips":["Keep Stdio::piped() paired with the take() calls","Add a test asserting the child's stdio configuration","Never refactor stdio setup without running describe_command tests"],"tags":["panic","process","stdin","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}