feat(shell): stderr redirects (2>, 2>>) and 2>&1 via std::io::pipe#48
Merged
Conversation
WHAT: The confined engine now supports stderr fd redirects. parse.rs: read_fd_redirect parses a bare fd touching a redirect op — 1>/1>>/0< alias the bare forms, 2>/2>> add Redirect::Stderr{path,append}, 2>&1 adds Redirect::StderrToStdout; fd>=3 and other dups (1>&2) refused. Command::stderr_disposition() resolves the effective stderr target (last wins): Capture | File | Stdout(merge).
WHY (shared pipe, no new dep): run_pipeline is rewritten onto std::io::pipe() (stable since Rust 1.87; toolchain is 1.96) for all non-file stdout wiring, so the writer can be CLONED for 2>&1 in any position — captured last stage, file-redirected, AND piped-to-next. The clone is moved into the child's stderr; the parent retains no pipe writers, so capture readers hit EOF on child exit (deadlock-free). 2> file targets are leash-checked (fs_write) in invoke before any spawn, like other redirects. No os_pipe crate.
TEST: parser (2>/2>>/2>&1 parsed; 1>/0< aliases; >file 2>&1 both-to-file; head -2 stays an arg; fd>=3 / 1>&2 / 2<&1 refused) + mocked (2> /etc/passwd denied before spawn; 2>&1 reaches spawner) + real (2> to a file; 2>&1 merges into captured stdout; 2>&1 before a pipe feeds stderr downstream). just check green; all prior pipeline tests still pass (the std::io::pipe rewrite preserved basic piping). Fixes #45.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMGPEApE4XfwgMhgFbRn6c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #45 (a #34 follow-up). Adds stderr fd redirects to the confined engine —
2>/2>>(to file) and2>&1(merge into stdout) — via a shared pipe with no new dependency.The shared pipe
std::io::pipe()is stable on our toolchain (Rust 1.87+; we're on 1.96).run_pipelineis rewritten onto it for all non-file stdout wiring, so the writer can be cloned and handed to both the child's stdout and stderr → a real2>&1merge that works in every position: captured last stage, file-redirected, and piped-to-next (cmd 2>&1 | next). The parent keeps no pipe writers after spawn, so capture readers hit EOF on child exit (deadlock-free). Noos_pipecrate.What
parse.rs—read_fd_redirect:1>/1>>/0<alias the bare forms;2>/2>>→Redirect::Stderr;2>&1→Redirect::StderrToStdout.fd ≥ 3and other dups (1>&2) refused.Command::stderr_disposition()resolves the effective target (last wins): Capture / File / merge-to-Stdout.shell_tool.rs— thestd::io::piperewrite;2> filetargets leash-checked (fs_write) ininvokebefore any spawn.Testing (fully mocked + deep)
2>/2>>/2>&1parsed;1>/0<aliases;> out 2>&1both-to-file;head -2stays an arg (digit not touching a redirect op);3>/1>&2/2<&1refused.2> /etc/passwddenied before spawn;2>&1reaches the spawner.2>to a file;2>&1merges the error into captured stdout;2>&1 | catfeeds stderr downstream.Test plan
just checkgreen (fmt + clippy all-features & no-default-features + workspace tests); every prior pipeline test still passes (the rewrite preserved basic piping).🤖 Generated with Claude Code