Skip to content

Next Release - #4576

Open
rtk-release-bot[bot] wants to merge 103 commits into
masterfrom
develop
Open

rtk-release-bot[bot] wants to merge 103 commits into
masterfrom
develop

Conversation

@rtk-release-bot

@rtk-release-bot rtk-release-bot Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Feats

Fix

  • fix(recall): widen content hash to 64 bits so colliding outputs are kept #4464 — Closes #3948 (to verify)
  • fix(js): handle bun test --coverage and --bail #4440
  • fix: check hook integrity for rewritten commands #4444 — Closes #4433 (to verify)
  • fix(gain): exclude zero-input rows from exec time totals #4070 — Closes #4003 (to verify)
  • fix(pytest): report setup errors in the summary #4466 — Closes #4426 (to verify)
  • fix(pytest): validate trailing summary line #3355 — Closes #3348 (to verify)
  • fix(init): only offer the proxy when rtk-proxy can run, and remove its base URL on uninstall #4622 — Closes #4604 (to verify)
  • fix(ruff): preserve analyze graph JSON output #3942 — Closes #3929 (to verify)
  • fix(permissions): fail closed when a settings file cannot be read or parsed #4287
  • fix(init): preserve user prose during uninstall #4405
  • fix(hooks): leave git unrewritten in Claude Code managed worktrees #3879 — Closes #3864 (to verify)
  • fix(hook): skip background checks in hook commands #4308 — Closes #4297 (to verify)
  • fix(git): no log cap notice under git's usage on git 2.56 #4578
  • fix(build): send the MinGW linker --stack, not MSVC's /STACK: #3613
  • fix(cli): hand -h, --help and help to the tool each subcommand wraps #4348 — Closes #1474 (to verify), #3663 (to verify)
  • fix(permissions): match deny and ask rules past assignments and wrappers (Permission rules: deny and ask miss commands behind an env assignment or a process wrapper that Claude Code strips #4281) #4284
  • fix(git): only announce the log cap on a walk git ran, and count it right #4148

Other

jaideeppyne and others added 30 commits August 1, 2026 16:07
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>
Signed-off-by: AxelRay <110029405+axelray-dev@users.noreply.github.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
…parsed

A Claude Code settings file that exists but cannot be read or parsed adds a
catch-all Ask rule and a warning instead of being skipped. Refs #4282.
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.
pytest format_session_duration appends " (H:MM:SS)" once a run exceeds
a minute. Allow that optional clock so long suites are still parsed.
Signed-off-by: tayfuryldz <yildiztayfur668@gmail.com>
Signed-off-by: tayfuryldz <yildiztayfur668@gmail.com>
Signed-off-by: tayfuryldzz <yildiztayfur668@gmail.com>
`bun test --coverage` prints its table and `--bail` prints "Bailed out
after N failures", and neither survived the summary extractor. The bail
case is the costly one: bun prints no count lines when it aborts, so the
summary read as a complete run of however many tests happened to execute
before the abort.

The table is tracked from its header to the next blank line rather than
by counting column separators, so a source frame holding `a || b` is not
mistaken for a row, and its rows are capped at CAP_LIST: one row per file
makes it the only part of the summary that grows with the repo.

Fixtures are real bun 1.3.14 runs.
Two shapes the bun fixtures did not reach. A run killed by SIGKILL
prints the banner and nothing else, so the raw-tail fallback is all
that stands between an aborted run and a summary that reads clean. And
40 failures in one run exercise MAX_RUNNER_FAILURES, which no bun
fixture was large enough to reach.

Both are real bun 1.3.14 captures. No behaviour change.
Bun sorts the coverage table by path, so capping it by a plain head kept
whichever files came first alphabetically and dropped the ones with a
gap, which is what the flag was passed for. On a 41 file project the only
row below 100% was last in the sort, so it never appeared and could not
be retrieved.

The table is buffered now and filled in priority order: its structure,
then the rows reporting a gap, then whatever still fits, each in bun's
own order. What the cap drops is announced as files, not table lines, and
written out through force_tee_tail_hint, so a capped run is recoverable
like the other capped lists in the tree.

The cap is pinned against real output over it: exactly the cap is shown,
the notice appears once and accounts for every dropped line, and a table
of exactly the cap announces nothing.
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.
content_hash() kept 12 hex characters (48 bits) of SHA-256, and the
store upserts on the hash, so two outputs sharing that prefix overwrote
each other. Keep 16 hex characters. Existing 12-character rows still
resolve by exact match, and shorter prefixes still resolve.

Closes #3948
A run with fixture, teardown or collection errors ended "1 passed,
1 error" (exit 1) but rtk printed "Pytest: 1 passed": the summary
parser had no arm for error(s) and the ERRORS section was skipped.
Count errors, keep the run off the all-green line, and render the
ERRORS blocks and ERROR short-summary lines as [ERROR] entries.

Fixes #4426
…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>
KuSh and others added 30 commits October 17, 2026 01:45
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
Port the seven missing operational-command entries and regression coverage
to current develop without importing unrelated commit history.
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
`rtk gain` summed `exec_time_ms` over every tracked row. Rows recorded
through the passthrough/fallback path carry no input (`input_tokens = 0`)
and their duration is the wrapped process's whole lifetime, so a single
interactive TUI launched as `rtk claude` adds hours of wall-clock time.
On the reporter's install 131 such rows were 99.7% of `Total exec time`
and inflated the average 865x (9m5s reported vs 1.15s of real filtering).

Only rows with captured input now count toward the exec-time total and
its average, in the summary and in the daily/weekly/monthly breakdowns
(text, JSON and CSV), including the TOTAL row of the period tables.
Command counts, token totals and savings percentages are unchanged.

Closes #4003

Signed-off-by: Raymond Riter <rpr2998@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: jaideeppyne <jaideeppyne1997@gmail.com>
A closing marker the user quotes after the RTK block must not extend the
removal over their prose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… parse failure

The read-failure warning and the delegate exit code are checked end to end
through `rtk rewrite` with `RTK_REWRITE_HOST=openclaw`, for a settings file
that cannot be read and one that cannot be parsed. A unit test checks that
deny rules from the other settings files still apply after an unparseable
one, as they already did after an unreadable one. The new test helpers say
why they would panic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(init): preserve user prose during uninstall
…ettings

fix(permissions): fail closed when a settings file cannot be read or parsed
Replay real `ruff analyze graph` and `ruff rule E501` output (ruff 0.16.6)
through `rtk ruff`: the graph comes back whole and valid JSON, other
passthrough subcommands keep the cap. The unit tests now use the same
captured graph instead of a hand-built one, and pin which invocations
the classifier exempts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-3929

fix(ruff): preserve analyze graph JSON output
fix(init): only offer the proxy when rtk-proxy can run, and remove its base URL on uninstall
fix(pytest): validate trailing summary line
fix(pytest): report setup errors in the summary
…time

fix(gain): exclude zero-input rows from exec time totals
…ntegrity

fix: check hook integrity for rewritten commands
The collision test now asserts both stored keys, so a key length other
than 16 fails it. A new test stores an output under a 12-char key, as the
previous release did, stores it again under the 16-char key, and checks
that each hint returns its own row instead of an ambiguous-prefix error.

The getting-started guide's example hint uses the 16-char form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-bail

fix(js): handle bun test --coverage and --bail
fix(recall): widen content hash to 64 bits so colliding outputs are kept
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working git next-release

Projects

None yet

Development

Successfully merging this pull request may close these issues.