Repository navigation
Conversation
Agents gate on exit status, and hooks replace a command with whatever `rtk rewrite` returns, so the rewrite must exit with the raw command's code. Run a matrix of rewritten commands (git, ls, grep, wc, cat/head -> read, find, plus `&&`/`;` chains and python3 one-liners when available) raw and rewritten in a sandboxed repo and require identical exit codes. A second test fails if the cases stop exercising the rewrite path, so the check cannot silently degrade into comparing `sh` with itself. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcTsSr2cKxstnvgXk2bP88
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unresolved findings affect test determinism and claimed rewrite coverage.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
What changed in this PR
Adds a Unix-only integration test verifying that rtk rewrite preserves command exit codes across tools and shell chains.
Changes:
- Creates an isolated temporary Git sandbox.
- Compares raw and rewritten command statuses.
- Guards against rewrite rules silently disappearing.
| File | Summary |
|---|---|
tests/rewrite_exit_code_test.rs |
Adds exit-code propagation coverage. Five unresolved moderate findings concern path control, skipped Python and semicolon cases, and incomplete environment isolation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
KuSh
left a comment
There was a problem hiding this comment.
Round 1 — CHANGES REQUESTED
Claim: an integration test that pins rtk rewrite's exit-code faithfulness: each command in a matrix exits the same raw and as its rewrite, for a rewritten tool's own status and for a status flowing through && / ; lists.
Scope: accept. A regression test for the #3492 / #2974 class is worth merging as stated.
Ran: rtk rewrite then sh -c on the merge of 62becfe onto develop e12ca86. I ran the 16 PR cases plus 12 candidate rows, comparing raw and rewritten exit codes. Invariants: under the hook (rtk rewrite is the hooks' own call; every rewrite answers 3, ask, in the sandbox) and every decision (6 product mutations). Savings: n/a, test-only.
Pins: 6 mutations of the product, 4 survive the PR's tests (git failures swallowed, && printed as ;, || printed as &&, four tools dropping out of the rewrite)
CI @62becfeb: 3 failing: test (ubuntu-latest), test (macos-latest), test (windows-latest), all failing core::test_isolation::tests_that_spawn_rtk_isolate_their_data
Blocks merge (3)
tests/rewrite_exit_code_test.rs:59: the sandbox builds its own isolation instead of usingtests/common. This affects all three spawn sites: the setup git,Sandbox::commandandSandbox::rewrite.- CI red. Since develop
e0d99ab5(2026-09-30, after this branch's baseb748a5f), atests/file may not nameCARGO_BIN_EXE_rtk. Merged onto develop,tests_that_spawn_rtk_isolate_their_datafails withtests/rewrite_exit_code_test.rs: CARGO_BIN_EXE_rtk. That is the red on all threetestjobs, and it stops the run before this file's tests execute. - Writes to the developer's repository. The children inherit the developer's environment. Git exports
GIT_INDEX_FILEto a pre-commit hook, as an absolute path to.git/index.lockundergit commit -a. With it exported,Sandbox::new'sgit add a.txtstageda.txtinto that index: a probe repository's index went fromkeep.txttoa.txt+keep.txt. - Fails depending on the machine. A developer who exports
CLAUDE_CONFIG_DIRwith a deny rule such asBash(ls:*)gets a redmost_cases_actually_go_through_a_rewrite, becausertk rewriteexits 2. In the same run,rewritten_commands_keep_the_raw_exit_codepasses while silently skipping everylscase. - A fix must build every child through
common::rtk_command(),common::isolate_rtk(&mut cmd)andcommon::temp_git_repo()/common::isolate_git(). It must take rtk's directory forPATHfromcommon::rtk_command().get_program(), astests/hook_decision_protocol_test.rs:156does. - Rows: the guard is green on the merge; the probe index is unchanged with
GIT_INDEX_FILEandGIT_DIRexported; the test passes with thatCLAUDE_CONFIG_DIRexported.
- CI red. Since develop
tests/rewrite_exit_code_test.rs:112: unpinned: no case makes a rewritten tool fail where its status decides the result. No git command fails (git diff --exit-code HEADruns on a clean tree, so it exits 0). No failing tool heads an&&list, and there is no||.-
Evidence: three mutations pass 2/2.
main.rsexiting 0 for everyrtk gitfailure;rtk rewriteprinting&&as;; and printing||as&&. -
The test must run and match these rows:
Command Exit git log --no-such-flag128 git diff --exit-codewith a tracked file changed1 git show no-such-rev && sh -c 'exit 9'128 ls /nonexistent-rtk-path && sh -c 'exit 13'2 on Linux ls /nonexistent-rtk-path || sh -c 'exit 13'13 ls a.txt || sh -c 'exit 13'0 -
Each of the three mutations must turn it red. All six rows rewrite and match on develop today, so they add coverage, not new failures.
-
tests/rewrite_exit_code_test.rs:174: cases skip silently.true; sh -c 'exit 12'andsh -c 'exit 5'are never rewritten (rtk rewriteexits 1). The loopcontinues past them, so they test nothing, including the;list the description names.- The guard's
rewritten.len() * 2 >= CASES.len()lets 7 of the 14 cases drop out unnoticed. Evidence:rtk rewritedeferring oncat/head/wc/find, plusrtk read/wc/findexiting 0, passes 2/2, with 8 of 14 cases still rewritten. - The test must assert each case's rewrite one by one, as it already does for
PYTHON_CASES, and cover;with a rewritten head, e.g.git status; sh -c 'exit 12'(12). That mutation must turn it red.
Checked and correct (no need to re-verify)
- On the merged binary, every PR case that rewrites (14 of 16) exits the same raw and rewritten. That includes both compound Python cases.
- The description's own regression check reproduces. With
main.rsswallowing codes above 1, the test fails namingls /nonexistent-rtk-path(2 vs 0). With every failure swallowed, it names 5 cases. - The Copilot thread on bare
python3 -cis fixed in 62becfe: the bare cases are gone, their passthrough is documented, and each compound Python case is asserted rewritten. - With
XDG_DATA_HOMEexported, the test writes nothing there.
Your questions
None asked.
Next
Merge develop and move the sandbox onto tests/common (1). Add the failing-head and || rows (2). Assert each case's rewrite instead of a 50% threshold (3). After that, this merges once CI is green.
Threads: 3 open (the blockers above), 1 resolved this round (Copilot's Python thread, fixed by 62becfe).
…it-code-regression-test
…he python probe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KuSh
left a comment
There was a problem hiding this comment.
Round 2 — APPROVED
Claim: an integration test that pins rtk rewrite's exit-code faithfulness: each command in a matrix exits the same raw and as its rewrite, for a rewritten tool's own status and for a status flowing through && / || / ; lists.
Scope: accept (round 1, frozen).
Ran: rtk rewrite then sh -c on 7087fe9 merged onto develop 5eb0509, all 21 cases with their pinned raw codes. Hostile environments, each a separate run: GIT_INDEX_FILE + GIT_DIR at a probe repository; CLAUDE_CONFIG_DIR denying Bash(ls:*) and Bash(git:*); LC_ALL=de_DE.UTF-8; RTK_NO_TOML=1 RTK_TEE=0; TMPDIR inside a git repository; busybox ls/find first on PATH; a python3 shim that resolves through the caller's HOME. Invariants: under the hook, every decision. Savings: n/a, test-only.
Pins: 7 product mutations. The head catches 6. The seventh, rtk git diff always exiting at least 1, is closed by the fixup below.
CI @7087fe9f: 12/12 green, and the new test ran on macOS. @b7d9f5d00: 15/15 green.
Blocks merge (3, frozen at round 1): all fixed in 7087fe9
- Isolation. All 6
test_isolationchecks are green on the merge. The probe repository's index is byte-identical after a run withGIT_INDEX_FILEandGIT_DIRexported, and the test passes with deny rules inCLAUDE_CONFIG_DIR. - Failing heads. Swallowing git failures, printing
&&as;and printing||as&&each turn the test red now. - Silent skips. Every case must rewrite. Dropping
cat/head/wc/findfrom the rewrite fails both tests.
Fixup b7d9f5d: what it changes
- The
lsmissing-operand code is probed once in the sandbox and must be 1 or 2. It used to be keyed oncfg!(macos). With busyboxlsfirst on PATH (exit 1), the head failed withraw fixture exit changed: ls /nonexistent-rtk-path. A macOS shell with GNU coreutils first on PATH is the mirror case. - The
python3probe runs in the sandbox's own environment. A version-manager shim that resolves through the caller'sHOME(the asdf/mise pattern) passed the old probe, then exited 126 inside the sandbox and failedgit status && python3 …. - A clean-diff row:
git diff --cached --exit-code && sh -c 'exit 7'(7). My round-1 row made the fixture dirty, which left no case wheregit diff --exit-codesucceeds, sortk git diffalways exiting at least 1 passed the suite. This row restores that coverage. - The rewrite-guard test iterates both tables directly. It now checks the Python rows' rewrites even where
python3cannot run, and a badlsprobe no longer fails it. .expectreplaces.unwrap()for the binary's directory, and three comments now describe the fixture and the table as they are.
Checked and correct (no need to re-verify)
- On the merge, every row's raw code matches its pin, and its rewrite exits the same.
- The full suite on the merge is green apart from one develop test that needs a local Go toolchain and fails the same way without this PR.
Your questions
None asked.
Next
Nothing: fixup b7d9f5d pushed and code-reviewed. Approving on green CI.
Threads: 0 open. The 3 round-1 threads are resolved, and each is fixed in 7087fe9.

Refs #3492 (also the #2974 / #2735 class).
What
Adds
tests/rewrite_exit_code_test.rs, an integration test that treats exit-code propagation through hook rewrites as an invariant, which is what #3492 asks for: every command in a matrix is run twice in a sandboxed git repo, once raw and once as whateverrtk rewritereturns, and the two exit codes must match.The matrix pairs a rewritten tool (
git status/log/diff,ls,grep -r,wc,cat->rtk read,head->rtk read --head-lines,find) with a failing or succeeding step, so it covers both the tool's own status and a status flowing through an&&/;chain. Thepython3 -c/SystemExitcases from #3492 are included whenpython3is on PATH.A second test guards the first: if rule changes ever stop these commands from being rewritten, the exit-code check would silently compare
shwith itself and keep passing, so it asserts that at least half of the cases still go through a rewrite.The sandbox points
HOME,XDG_CONFIG_HOME,RTK_DB_PATHandRTK_TEE_DIRat a tempdir, following the existing integration tests, so running it doesn't touch the developer's own config or tracking DB.Does it catch a regression?
Checked by temporarily changing the final
std::process::exit(code)inmain.rsto swallow codes above 1. The test fails and names the command:With the code unchanged, both tests pass on
develop(b748a5f).Verification
cargo fmt --all,cargo clippy --all-targetsandcargo test --allpass locally (Linux). The file is#![cfg(unix)]like the other process-spawning integration tests.