{"record":{"id":"c6dba2ce5504e0e8","repo":"jdx/mise","slug":"command-output-pipes-did-not-close-within-pipe-dr","errorCode":null,"errorMessage":"command output pipes did not close within {pipe_drain_timeout:?}","messagePattern":"command output pipes did not close within (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cmd.rs","lineNumber":1442,"sourceCode":"                        #[cfg(unix)]\n                        signal_process_tree(id, nix::sys::signal::Signal::SIGKILL);\n                        #[cfg(windows)]\n                        kill_process_tree(id);\n                        let _ = wait.await;\n                        return Err(err);\n                    }\n                }\n            }\n        }\n        let drain_deadline = Instant::now() + pipe_drain_timeout;\n        loop {\n            let remaining = drain_deadline.saturating_duration_since(Instant::now());\n            if remaining.is_zero() {\n                #[cfg(unix)]\n                signal_process_tree(id, nix::sys::signal::Signal::SIGKILL);\n                #[cfg(windows)]\n                kill_process_tree(id);\n                bail!(\"command output pipes did not close within {pipe_drain_timeout:?}\");\n            }\n            let output = match tokio::time::timeout(remaining, rx.recv()).await {\n                Ok(Some(output)) => output,\n                Ok(None) => break,\n                Err(_) => {\n                    #[cfg(unix)]\n                    signal_process_tree(id, nix::sys::signal::Signal::SIGKILL);\n                    #[cfg(windows)]\n                    kill_process_tree(id);\n                    bail!(\"command output pipes did not close within {pipe_drain_timeout:?}\");\n                }\n            };\n            if let Err(err) = consume(output) {\n                #[cfg(unix)]\n                signal_process_tree(id, nix::sys::signal::Signal::SIGKILL);\n                #[cfg(windows)]\n                kill_process_tree(id);\n                return Err(err);","sourceCodeStart":1424,"sourceCodeEnd":1460,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/cmd.rs#L1424-L1460","documentation":"After the command process exits, execute_hashes_async_with_drain_timeout waits up to pipe_drain_timeout for the stdout/stderr pipes to close (all buffered output consumed). If the deadline expires or a remaining-drain recv times out, the process tree is SIGKILLed and this error is thrown. It means grandchildren/descendants still hold the pipes open, so output may be incomplete.","triggerScenarios":"The main command exits but spawned background children or grandchildren inherit stdout/stderr and keep the pipes open beyond pipe_drain_timeout; the recv timeout in the drain loop elapses with remaining time at zero.","commonSituations":"Tasks that daemonize or launch background servers (watchers, dev servers) that inherit the parent's output pipes; scripts spawning long-lived children without redirecting their output.","solutions":["Redirect background children's output to files or /dev/null (e.g. `child --daemon > /dev/null 2>&1 &`) so the pipes close when the parent exits","Increase the pipe_drain_timeout at the call site if legitimate slow output needs more time","Fix the task so it waits for or detaches its children properly instead of leaving them holding the pipes"],"exampleFix":"// before\nmy-task &  # background child keeps stdout/stderr pipe open -> drain timeout\n// after\nnohup my-task > /tmp/my-task.log 2>&1 < /dev/null &","handlingStrategy":"try-catch","validationCode":"// prefer tasks whose children detach their own output\n// grep your script for background spawns inheriting stdout:\n//   grep -nE '(^|[^>]*)&( |$)' script.sh","typeGuard":null,"tryCatchPattern":"match cmd.execute_hashes_async(MAX).await {\n    Ok(h) => Ok(h),\n    Err(e) if e.to_string().contains(\"pipes did not close\") => {\n        // kill orphans, then retry with children redirected\n        cleanup_orphans();\n        cmd.execute_hashes_async(MAX).await\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Always redirect background children: `cmd > log 2>&1 < /dev/null &`","Never leave daemons/watchers holding inherited stdio in tasks","Set pipe_drain_timeout generously for tasks that legitimately spawn children","Audit task scripts for `&` background spawns without redirection"],"tags":["process","pipe","timeout","task"],"backgroundTag":"request-timeout","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"}