fix(err,test): preserve argument quoting and exit codes in rtk err/test#3002
Open
albatrossflyon-coder wants to merge 1 commit into
Open
Conversation
`rtk err <cmd>` and `rtk test <cmd>` built their sh -c/cmd /C command
string with `argv.join(" ")`. The OS already split argv into correct
elements, but the naive join throws that boundary information away --
an argument containing shell metacharacters (`node -e 'console.log("a")'`,
`python3 -c "print(...)"`) gets flattened, then the shell re-splits it
on those same characters and fails, most commonly with a syntax error.
On top of the quoting loss, a failure like this was being reported as
exit 0 -- not because exit-code propagation itself was broken (it
correctly reads the child's real status), but because there was no
child failure to correctly propagate: the shell's own diagnostic
about the malformed command was easy to mistake for the target
command failing, when actually the target command was never run
at all in a form the shell could execute successfully.
Add quote_shell_arg/shell_quote_join to re-quote each argument before
joining, so the shell reconstructs the exact original boundaries.
Windows needed one extra step beyond quoting: std::process::Command's
own arg-escaping was re-escaping the already-quoted command string a
second time when spawning cmd.exe, corrupting it. Using raw_arg (which
passes the string through untouched) for the Windows /C invocation
fixes that -- verified live with quotes, spaces, semicolons, and
parens in one argument, both for a passing command and one that exits
non-zero after printing partial output.
Fixes rtk-ai#2985
tapheret2
reviewed
Jul 15, 2026
tapheret2
left a comment
There was a problem hiding this comment.
Review: fix(err,test): preserve argument quoting and exit codes in rtk err/test
Files: src/cmds/rust/runner.rs, src/main.rs
Size: +118 / -20
Notes
- Static pass on the patch: no obvious blockers from the diff alone.
Thanks @albatrossflyon-coder — independent review on the patch.
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.
Summary
Fixes #2985.
rtk err <cmd>andrtk test <cmd>build theirsh -c/cmd /Ccommand string withargv.join(" "). The OS already splits argv into correct elements by the time this process sees them, but the naive join discards that boundary information. An argument containing shell metacharacters —node -e 'console.log("a")',python3 -c "print(...)"— gets flattened into the join, and the shell re-splits it on those same characters when re-parsing the string.On the exit-code half: I don't think exit-code propagation itself is actually broken —
run_streamingcorrectly reads and returns the child's real exit status (verified this is the same code path I already fixed for #2994 in #2997). The exit-0 symptom in the issue's repro is what happens when there's no child failure to propagate: the shell's own syntax-error diagnostic looks like a command failure, but the target command was never actually invoked in a form the shell could execute — there's nothing non-zero to report because nothing that ran actually failed. Fixing the quoting fixes this too, verified below.Fix
Added
quote_shell_arg/shell_quote_jointo re-quote each argument before joining, so the shell reconstructs the original argument boundaries instead of re-splitting a flattened string.Windows needed one extra step beyond quoting.
std::process::Command's own argument-escaping was re-escaping the already-quoted command string a second time when spawningcmd.exe— corrupting it beforecmd.exeever saw it. Usingraw_arg(passes the string through untouched, no extra escaping layer) for the Windows/Cinvocation fixes that. Without it I could reproduce a different broken-quoting failure than the one reported — same root cause, different platform-specific escaping bug.Verified live
A genuinely failing command with quotes, semicolons, and parens all in one argument:
Also confirmed
python -c "print('hello world')"and that legitimate shell features (a plainrtk errinvocation without special characters) are unaffected.Testing
quote_shell_argleaves safe args unquoted / quotes args with metacharacters / quotes args with spaces;shell_quote_joinpreserves argument boundaries (the exact regression scenario) / handles empty argvcargo test: 2449 passed, 8 ignored, 0 failedcargo clippy --all-targets -- -D warnings: cleanNote
I tested this on Windows (not the macOS environment in the original report), so I can't directly confirm the POSIX
sh -cbranch against a real POSIX shell —quote_shell_arg's non-Windows path uses the standard single-quote-wrap-with-'\''-escape technique, which is the textbook-correct approach for this, but flagging that a maintainer with a Unix box handy might want to double check it directly.