Repository navigation
Next Release - #4576
Next Release#4576rtk-release-bot[bot] wants to merge 60 commits into
Conversation
The Windows stack-size reservation had two problems that only surface off
the MSVC toolchain.
`#[cfg(windows)]` in a build script describes the machine running the
build, not the machine the binary is for, so cross-compiling to Windows
from Linux silently skipped the flag while a Linux-targeted build on a
Windows host wrongly emitted it. Key the check off CARGO_CFG_TARGET_OS
instead, which is the target.
The flag itself is also toolchain-specific. `link.exe` understands
`/STACK:`, but the MinGW `ld` behind `x86_64-pc-windows-gnu` does not --
it reads the argument as a filename and fails the link with:
ld.exe: cannot find /STACK:8388608: No such file or directory
Select `-Wl,--stack,<bytes>` when CARGO_CFG_TARGET_ENV is `gnu` and keep
`/STACK:` otherwise. CI never caught this because windows-latest defaults
to MSVC.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
Clap adds -h/--help and a help subcommand to every subcommand it builds. On a subcommand that exists only to compress another tool's output those tokens are not RTK's to answer: grep -h is --no-filename, ls -h and tree -h are --human-readable, and git help log is a real subcommand. RTK replied with its own usage on stdout at exit 0, so an agent that only ever typed grep -h (the hook adds the prefix) read RTK's help as grep's matches. Only psql and ctest had opted out, one bug report at a time. release_meta_flags now walks the clap tree and releases those flags on every subcommand that forwards to a native tool, detected structurally rather than from a list. RTK's own commands keep their help, and rtk help <cmd> still reaches RTK's usage for a wrapper. Forwarding the flag exposed a second half: filters that summarise found no diagnostics in a help page and printed their empty summary instead, so cargo test --help returned nothing at all. args_utils::asks_tool_for_help guards those entry points and defers to runner::run_passthrough. Fixes #4198
Drops the docs and awareness-text changes: rewording the "always safe" claim in the agent instructions is a separate concern from the flag collision, and the README and TECHNICAL edits were not needed to ship the fix. release_meta_flags no longer recurses. disable_help_flag, disable_version_flag and disable_help_subcommand are all global settings in clap, so one call already covers the whole subtree. asks_tool_for_help now reads -h as a help request. It only read the long spellings before, so cargo build -h still reached the build filter and came back as "cargo build (0 crates compiled)". ls and tree use the long-only form, since their -h is --human-readable.
The guard was repeated at 41 filter entry points. 38 of those modules already run their child through runner::run, which holds the built Command and so can answer the question itself: requests_help reads the program name and the argv before the boundary, and swaps the mode to Passthrough. That leaves explicit guards only where the filter runs the child itself rather than through the runner: git, find, pip, uv and dotnet. requests_help_args is the same rule for those callers, so there is one definition of what a help request is. -h is a request everywhere except psql, ls, tree and grep, which spell something else with it. RTK's own package-runner prefix (pnpm exec --, npx --) is skipped so the -- it ends with is not read as the caller's boundary.
Two layers, mirroring the two halves of the bug. Over the clap tree: every wrapper path crossed with -h, --help, -V, --version and help, dropped at every position of nine argv shapes, asserting RTK never answers with its own usage and that the token still reaches the leaf. Roughly 14k parses, read off the built tree so a new subcommand joins on its own. The inverse is pinned too, or releasing everything would satisfy it. Over real binaries: rtk <tool> --help spawned against the tool itself for 24 pairs, comparing exit code and asserting the page was not collapsed to a summary. Tools that are not installed are skipped, and commands are spawned directly so a grep shell function cannot stand in for the binary. Both were mutation-checked. Removing release_meta_flags fails the first with "RTK answered `rtk ls -h` itself"; removing the runner guard fails the second with "rtk cargo build -h returned 32 bytes of the tool's 3241"; dropping tree from DASH_H_IS_NOT_HELP fails the filtering check.
Three fixes to the tests added in the previous commit, all found by CI. semgrep's dynamic-command-execution rule forbids Command::new(<variable>) anywhere under src/, test modules included. The runner tests built a Command only to feed requests_help, so they now exercise requests_help_args, which takes the tool name as a string, and one adapter test keeps a literal program. Windows names the binary rtk.exe, so the "Usage: rtk " marker never matched. That failed the positive assertion and, worse, made the negative one pass for the wrong reason. The marker now stops at the stem. on_path joined PATH entries with a bare name, which on Windows finds the extensionless npm script rather than npm.cmd and fails the spawn with error 193. It now walks PATHEXT there. The cases whose Windows namesake is an unrelated program (find, tree) or needs a Unix toolchain (grep, ls, wc) are skipped, and the two tests asserting GNU/BSD flag semantics are unix-only.
Signed-off-by: tayfuryldz <yildiztayfur668@gmail.com>
Signed-off-by: tayfuryldz <yildiztayfur668@gmail.com>
Signed-off-by: tayfuryldzz <yildiztayfur668@gmail.com>
Resolves the test-import conflict in src/main.rs by keeping develop's test_isolation import and dropping clap::Parser, which this branch no longer needs. The help fuzz now spawns through common::rtk_command() rather than naming CARGO_BIN_EXE_rtk, so the child writes to the test's scratch data directory instead of the contributor's own. core::test_isolation fails the suite on a spawn that skips it (#3758), which is what turned the merged branch red.
…n capture too Review round 1 on #4348. Blocker 1(a): a help request still reached filters that run the child through exec_capture rather than runner::run, and several answered with a plausible result: jest returned 2161 bytes of its 26881, pnpm outdated reported "48 outdated packages" parsed out of a help page, wget reported a download. The guard now also sits in stream::capture, which every exec_capture caller passes through. exec_capture_probe is the way out for a command RTK issues itself: a version probe asks --version to read the answer, not to show it, and golangci runs one. Blocker 1(b) and 1(c): asks_for_usage goes through arg_tokenizer instead of comparing raw strings, per rule 6 in rust-patterns.md. A per-tool spelling picks the dialect, so dotnet's -? and /? and Go's -help are read as the atomic names they are; a flag carrying a value no longer counts, so `cargo install x --version 1.2.3` stays filtered; and a tool that forwards past -- gets that region scanned as the argv it is, so `cargo test -- --help` is libtest's usage while `grep -- --help` is still a pattern. git and dotnet keep their own guards rather than relying on capture: by then RTK has injected its globals, and git's --no-pager turns the man page into a one-screen usage. pip's guard goes, covered by capture. kubectl/oc `extra` now carries only a usage request. Anything else runs as the caller typed it, matching kubectl's own "unknown command" rather than silently reaching a `get` pinned to -o json. Blocker 2: pins for what the mutations survived. The forwarded git argv including globals and the stash subcommand, every git_name arm, forwarded_dotnet_args, the dotnet spellings, the k8s alias split, RTK's own help on the commands that wrap nothing (read, diff, log, env), the forwarded boundary and the value forms. The integration check compares the tool's own first line rather than a length, so a page from the wrong subcommand fails.
Moving the scan onto the tokenizer brought short-flag clustering with it, and that read the h inside another flag as a request: `find -maxdepth 2` came back as raw find output instead of the compressed listing, and `mvn -Dhttp.proxyHost=…` and `gradlew -Dhttp.x=1` carry one the same way. A clustered h is almost never the flag, so it counts only when the argument is exactly `-h`. The cost is `cargo -qh`, which no longer registers; the tools that spell help with a single-dash long name (`go -help`, `dotnet -?`) are unaffected, since they are read in the MSBuild dialect where the name is atomic.
On Windows this harness reaches pip.EXE where rtk reaches pip, so the usage line the two print differs by that spelling while the page is the same. The anchor is compared with it removed. The check still fails on a page from the wrong subcommand, which is what it is for.
…ight `walk_exceeds_limit` asks git whether `rtk git log`'s ten-commit cap actually cut anything. It was wrong in both directions: silent on walks the cap had gutted, and loud on walks it never touched. Two kinds of walk, two questions. Without a diff-based filter (`-S`, `-G`, `--diff-filter`, `--find-object`) the probe skips past the limit and asks whether anything is left. With one it has to count instead, because git applies `--skip` before such a filter and `--max-count` after it. Most of what follows is the counting branch getting an honest count, and the notice being owed at all. What the probe forwards. It may drop what only decides how git prints, and must keep what decides which commits come back -- a distinction the flags do not respect. `--line-prefix` and `--graph` are dropped because both put something in front of every record, and `--graph` draws its rail with the characters a patch begins its lines with (`|` before a diff body, `-` before a removed line), so no trimming rule serves both. But `--graph` also turns on parent rewriting, which decides which commits come back: one path here holds a single commit without it and fifteen with it, so the probe asks for `--parents` instead, which turns on the same rewriting and prints nothing beside a `%H` record. `--reverse` is dropped because it moves `--max-count`: git drains the whole walk before showing anything, so the count bounds the walk rather than what came through the filter, and eleven records became six against a selection of 836. `--check` joins `--exit-code`, whose non-zero exit reports on the diff rather than on the command. And `-c`/`--cc`/`--remerge-diff` and every `--diff-merges` format but `off` are kept whenever the walk selects by diff: they give merge commits a diff, and a diff-based filter selects on the diffs the walk produces. On a repository whose merges carry content from neither parent, `git log -S evil` selects nothing where `git log -c -S evil` selects 14. Off such a walk they are still dropped, which spares a configured `diff.external` the eleventh run of a ten-commit window. The user's own `--skip` is now reproduced in the counting branch too. RTK's has to win, so the user's is stripped and re-applied -- but only the other branch re-applied it, and the count covered commits they had already skipped past: `--skip=20 -S NEEDLE` leaves five commits and announced a cap of ten. What counts as a commit in the answer. Records are split on NUL as well as newline, because `-z` terminates them that way and the whole walk had been read as one line matching nothing. A record is a commit only at an object name's exact length -- `%H` does not abbreviate, for neither `--abbrev-commit` nor `core.abbrev` -- which keeps out the paths `--name-only -z` puts in a record of their own, where a file called `cafebabe01` had counted as a commit. And only at column 0, which keeps out the diff context of a probe that kept a merge-diff flag, in the case where that context is itself full-length hex: a file of checksums. Last, the notice is gated on git not having refused. The probe prunes `--pretty`/`--format`/`--output`, so it can succeed on arguments git rejected outright, and the notice landed under git's own `fatal:` line. Failure is not that test: git refuses with 128, 129 or 128 + n and keeps the small codes for what the diff machinery found in a walk it ran and printed in full -- 1 for `--exit-code`, 2 for `--check`. The strip these go through is the one the `diff`/`show` header already used, lifted behind a predicate so the probe can widen it without widening that header's own idea of patch shape. Going through it is what lets `-c` be dropped at all: a short flag shares its argument with whatever was clustered onto it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…it-code-regression-test
Review round 2 follow-up, found by differential fuzzing against develop. git's cap-notice probe appends the caller's args to an argv RTK built, so a clustered -h reached it and the guard answered for the probe: `git log -hp` printed git's usage twice and `-ph` returned 129 where git returns 128. The probe and the cat-file blob probe now capture unguarded. `--help=x` is a request again. Only a separate value disqualifies one, so `cargo install x --version 1.2.3` still filters while `jest --help=x` returns jest's own 1936 bytes instead of 95. git's -m, --message, -F and --file carry values, so `git commit -m "--help"` is a message and stays filtered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three conflicts, all additive on both sides: src/main.rs keeps develop's proxy_label alongside this branch's K8sAlias version of build_k8s_namespace_args. go_cmd.rs and golangci_cmd.rs keep develop's shell imports alongside exec_capture_unguarded. The help fuzz now builds its native side with develop's common::native_command, so both sides of every comparison run in the same environment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The check compared RTK's output against half the size of `df -h`, which is 1627 bytes here and 436 on the CI container, where a 438 byte summary then read as unfiltered. It now asserts the property instead: the output differs from df's own and starts with RTK's marker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The err/summary pin used `df -h`, which is 436 bytes on a CI runner and 1.6 KB on a laptop. RTK rightly returns output raw when summarising it would be larger, so on ubuntu the assertion read RTK's correct fallback as a guard failure. Generate a 60 directory tree and probe `du -h` on it instead: the summary is 258 bytes against 1450 raw with short /tmp paths, so the marker means the guard held rather than that the input was long. Mutation checked: making the guard ignore `unguarded_help` fails it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tion Every Windows target reports target_env `msvc` or `gnu` (gnullvm reports `gnu`), so msvc is the only one with its own spelling. Matching on it makes any other environment take the `-Wl,--stack` form the gcc and clang drivers accept. Read the reservation back from the built rtk.exe's PE header in a Windows-only test, so a linker that rejects or drops the flag fails a test rather than shipping a 1 or 2 MiB stack. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The test matrix links windows with link.exe only, so a stack flag that GNU ld rejects (the failure build.rs used to have) cannot fail CI. Add a windows-latest leg that tests x86_64-pc-windows-gnu; the runner's host stays msvc, so build.rs also runs with host and target apart. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…p guard decisions `cargo install x --version=1.2.3` was read as a usage request and ran unfiltered, while `--version 1.2.3` stayed filtered; cargo reads both as the version to install. An attached value on a flag the value rule names is now a value too. Pins three decisions no test reached: git's message and file flags carry a value, `rtk test` reads the command it was given without the usage guard, and `rtk git log`'s cap-notice probe leaves a clustered `-h` to git. The help fuzz tests now create their directories under the scratch directory instead of fixed names in the system temp dir, which concurrent runs shared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bumps [encoding_rs](https://github.com/hsivonen/encoding_rs) from 0.8.35 to 0.8.42. - [Commits](hsivonen/encoding_rs@v0.8.35...v0.8.42) --- updated-dependencies: - dependency-name: encoding_rs dependency-version: 0.8.42 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ignore](https://github.com/BurntSushi/ripgrep) from 0.4.25 to 0.4.32. - [Release notes](https://github.com/BurntSushi/ripgrep/releases) - [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md) - [Commits](BurntSushi/ripgrep@ignore-0.4.25...ignore-0.4.32) --- updated-dependencies: - dependency-name: ignore dependency-version: 0.4.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.228 to 1.0.229. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](serde-rs/serde@v1.0.228...v1.0.229) --- updated-dependencies: - dependency-name: serde dependency-version: 1.0.229 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
fix(git): only announce the log cap on a walk git ran, and count it right
…ers (#4281) Claude Code matches a deny or ask rule past any leading assignment and past a fixed set of wrappers — `timeout`, `time`, `nice`, `nohup`, `stdbuf`, `command`, `builtin`, `noglob` and a bare `xargs` — so `Bash(git push *)` stops `HUSKY=0 git push` and `timeout 30 git push` as well as `git push`. RTK's gate matched the segment as written and nothing else, so every one of those forms fell through to Default; under a host that treats Default as its own decision, as #3909's OpenClaw does, they auto-apply. The rewrite already looked through the same prefixes: `timeout 30 git push` became `timeout 30 rtk git push` while the gate did not recognise `git push` inside it. That was two readings of which command a line runs, and the gap between them is this bug. The deny and ask loops now also try every reading `matching_readings` gives: the rewrite's own peels — its env peel, its keyword prefixes, its wrapper table — applied one prefix at a time, each assignment and each `env` word a prefix of its own, each intermediate command kept. Where both look through a prefix, the gate and the rewrite now read the same command behind it, and a rule may name any layer: `HUSKY=0 git push *` stops `env HUSKY=0 git push`, and `nohup HUSKY=0 git push` too, where denying is stricter than the shell, since nohup looks for a program named `HUSKY=0`. The readings start after the split's leftover grammar is gone, since `{ HUSKY=0 git push; }` reaches the gate as `{ HUSKY=0 git push`. They are computed once per segment, not once per rule, and stop after 32 prefixes; past that the command behind them is unread, so where a deny or ask rule exists the verdict is at least Ask and an allow rule on the outer wrapper cannot answer for it. With no deny or ask rule, none of this is read. Two wrappers the host strips were not in the rewrite's table, for a reason that still holds: `stdbuf` exists to unbuffer the wrapped command's output, which routing through rtk would buffer, and `xargs` runs whatever it is handed. Recognising a command is not the same decision as rewriting it, so they sit in a match-only table the rewrite never consults. `xargs` is peeled only in its bare form, as the host does; any option makes the wrapper grammar give up. The line is drawn at the shell. `env`, `exec` and `nocorrect` are peeled too, though the host's list stops short of them; each runs the next word as the command. A program's own arguments are not: `git -C . push` stays `git`, as the host documents, since reading past it would mean knowing every program's option grammar. Allow is deliberately left as it was. The host matches allow past known-safe variables only, and peeling a wrapper for it would let `timeout 30 <cmd>` inherit `<cmd>`'s allow rule — so the peel can only make a verdict stricter. The gate reads prefixes with the rewrite's own peels, so it stops where they stop; there it reads the command as before, so no verdict is looser than it was. A line setting more than 32 variables before its command now prompts where a deny or ask rule exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….0.229 chore(deps): bump serde from 1.0.228 to 1.0.229
…0.4.33 chore(deps): bump ignore from 0.4.25 to 0.4.32
📊 Automated PR Analysis
SummaryThis PR bundles the 'Next Release' changes, primarily a fix to the Review Checklist
Analyzed automatically by wshm · This is an automated analysis, not a human review. |
…g_rs-0.8.42 chore(deps): bump encoding_rs from 0.8.35 to 0.8.42
Bumps [windows-sys](https://github.com/microsoft/windows-rs) from 0.59.0 to 0.61.2. - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) --- updated-dependencies: - dependency-name: windows-sys dependency-version: 0.61.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
fix(permissions): match deny and ask rules past assignments and wrappers (#4281)
…-sys-0.61.2 chore(deps): bump windows-sys from 0.59.0 to 0.61.2
Bumps [which](https://github.com/harryfei/which-rs) from 8.0.1 to 8.0.6. - [Release notes](https://github.com/harryfei/which-rs/releases) - [Changelog](https://github.com/harryfei/which-rs/blob/master/CHANGELOG.md) - [Commits](harryfei/which-rs@8.0.1...8.0.6) --- updated-dependencies: - dependency-name: which dependency-version: 8.0.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
fix(cli): hand -h, --help and help to the tool each subcommand wraps
|
Adrien EPPLING seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
….0.6 chore(deps): bump which from 8.0.1 to 8.0.6
…p/actions/upload-artifact-7 chore(deps): bump actions/upload-artifact from 4 to 7
…p/actions/checkout-7 chore(deps): bump actions/checkout from 4 to 7
…p/googleapis/release-please-action-5.0.0 chore(deps): bump googleapis/release-please-action from 4.4.1 to 5.0.0
fix(build): send the MinGW linker --stack, not MSVC's /STACK:
…he python probe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
git 2.56 answers `-h` with its usage and exit 0, where every earlier release exited 129. `rtk git log` read "git refused the walk" from an exit code of 128 or more, so on 2.56 `rtk git log -hp` ran the cap probe after the usage text, the probe got the usage again, read it as commits left over, and appended "[rtk] capped at 10 commits" under it. `a_clustered_h_is_answered_by_git_once` fails this way on the macOS and Windows runners that moved to git 2.56 (2.55 still passes). A `-h` flag, alone or in a cluster, now counts as a walk that never ran. Only a flag token does: `--grep -h` and `-- -h` still run the walk and still get the notice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-regression-test test(hook): pin exit codes across rtk rewrite (#3492)
fix(git): no log cap notice under git's usage on git 2.56
run_cli moved from src/main.rs to src/lib.rs on develop, which also skips the hook-status warning for Commands::Hook already; the telemetry reorder is carried over to its new home. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maybe_ping is inert in any build without RTK_TELEMETRY_URL, test builds included, so no test could tell whether a hook command pings. The decision now lives in pings_telemetry, taken on the parse result, with one call site; the tests cover parsed hook commands and the invocations clap rejects (help, version, passthrough, a hook with a bad flag). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(hook): skip background checks in hook commands
Claude Code's worktree-isolation guard evaluates the command after the PreToolUse hook has rewritten it, and it can only verify git when it is spelled plainly. Every `rtk git …` the hook emitted was therefore refused as "cannot be shown not to be git", so no git command could run from a worktree-isolated session. The payload carries no isolation flag, but managed worktrees always live at `<repo>/.claude/worktrees/<name>`. When the payload cwd has that shape, the Claude hook adds `git` to the exclude list so it passes through untouched while the rest of the chain is still rewritten. Other hosts pass no cwd and are unchanged. Fixes #3864 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pin the worktree exclusion: another git verb, a non-worktree .claude directory, and the user's own exclude_commands kept alongside it. The doc comment now says a worktree placed outside .claude/worktrees is not detected, and the path check uses CLAUDE_DIR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(hooks): leave git unrewritten in Claude Code managed worktrees
Feats
Fix
Other