Repository navigation
Conversation
The breaking-change note covered Unix only, where nothing is lost. On Windows the `cmd /C` string also searched the working directory and carried ~30 builtins, so `rtk err dir` and a tool in the current directory now need the explicit form. Also documents the 126/127 answers and the `argv[0]` spelling change, and refreshes the `rtk test <cmd>` lines the awareness body, INSTALL and the six translated READMEs still showed.
The new `#[cfg(windows)]` test asserted that `c"d` survives through `cmd /C echo`, and the Windows runner reported `"a b" "c\"d"`: `cmd.exe` does not parse backslash escapes, so it echoed the MSVC/libuv encoding `child_args` applies verbatim. The encoding was right; the witness was wrong. Use `rtk` itself as the child. `rtk rewrite` prints the command string it was given, so the argument only comes back unchanged if the quote survived encoding on the way out and parsing on the way in — which is exactly what `child_args` is for (rtk-ai#3728). Isolated `HOME`/`USERPROFILE` so the verdict does not depend on the runner's settings files.
Round-2 review of rtk-ai#4125. Both defects were introduced by round 1's own fixes. **The `--shell` scan was argv-wide.** It ran for any command that failed clap parsing, so a passthrough tool carrying its own `--shell` stopped running: `rtk just --shell bash --version` and `rtk hyperfine --shell=none …` answered `unrecognized subcommand` at exit 2 where they used to run — and the hook emits that spelling itself, so it fired on the normal agent path. `err` and `summary` never needed it: they are in `RTK_META_COMMANDS`, whose check runs eight lines earlier. Only `test` does, because it deliberately shadows the POSIX binary and cannot join that list. **The `( … )` strip did not compose with the negation strip.** One pass of each left `! ( cmd )` with `(` as its program — not found, 127 — and the negation turned that into `i32::from(false)` = 0: a test wrapper reporting a pass for a command that never ran. Groups and negations are now stripped in one loop until neither applies, and a group is only peeled when its parentheses balance across the whole command, so `( a ) b ( c )` keeps both. All five spellings the code claims — `( cmd )`, `( ! cmd )`, `! ( cmd )`, `( ( cmd ) )`, `! ! ( cmd )` — match `sh`, and the test asserts the command actually ran rather than only the code. Four more from the review's optional list: - `classify_unrunnable` no longer reads an unsearchable parent as a missing program: `metadata` answering EACCES never established existence, so it reports 126 like `dash` instead of asserting 127. - The arity usage line names `rtk test`, not the POSIX utility `test`. - `program_name` names the platform default shell instead of the placeholder `command`, so `rtk run -c` with nothing on `PATH` says `rtk: sh: …`. - `rtk err` and `rtk summary` with no command answer clap's usage error at exit 2, where a doubled anyhow chain surfaced at exit 1. - The bad-executable-format code is `cfg`-gated: 8 is ENOEXEC on Unix, but on Windows `raw_os_error` is a Win32 code where 8 is ERROR_NOT_ENOUGH_MEMORY and the bad-format code is 193.
The two commits that let the tee path create the recall store fix rtk-ai#4219, which rtk-ai#4263 carries on its own. rtk-ai#4219 is an open design question (a tee install currently leaves no sqlite artifact behind), so it is decided there, and this branch stays on rtk-ai#4218. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`[]` is a clean run only when rtk chose `--output-format=json`. A user who asked for JSON must get `[]` back, so `rtk ruff check --output-format=json | jq` keeps working on a clean tree; register the pattern only when rtk injected the format. Move the choice between the raw capture and the filter summary into guard_stdout so it can be tested, list the exemption in guard.rs next to the one for `rtk diff`, and drop the pint registration: pint runs under a tee label, which never reads it, and a clean pint run prints a ~140-byte JSON that already loses to its summary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n-json-guard fix: allow clean-run summaries to bypass never_worse guard for injected JSON (fixes rtk-ai#4218)
The Claude and Cursor hooks turn rtk rewrite's exit 0 into an explicit allow, so an inherited RTK_REWRITE_HOST reaching them would auto-approve every rewritable command in another agent's session. The repo copies scrub it with `env -u RTK_REWRITE_HOST`, but copies already deployed keep the old text, and hook_check graded their version 3 as current, so nothing asked their owners to migrate. The Claude hooks move to version 4, so hook_check reports those installs as outdated and `rtk init -g` moves them to the in-process hook, which never reads the variable. A test now runs each of the three hooks the way the agent runs them, with the variable set, and checks the JSON they return in the keys that host reads: the rewrite must come back and must not carry an allow. It clears the environment before starting bash, so an inherited BASH_ENV or another rtk cannot stand in for the built one, and it keeps config, data and cache in the sandbox. Nothing covered the scrub before: the protocol harness strips the variable before spawning the binary, so removing it from all three hooks left the suite green. A second test keeps the shipped Claude hook and CURRENT_HOOK_VERSION in step. Code comments now state the invariant they stand for instead of pointing at review rounds. The Claude hooks' exit-code notes and `rtk rewrite`'s own exit table now say exit 3 covers both an ask rule and no rule, and the hooks say exit 0 means an allow rule matched: for a hook that scrubs the variable, no rule never yields exit 0. The OpenClaw docs say, as Claude Code's own do, that a deny rule matches the command as written and is not a security boundary, that an explicit ask prompts when RTK rewrites the command, and point at the exit-code table instead of pi's README, which documents the opposite convention. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-permissions fix(openclaw): let the host own approval without losing RTK's deny gate
…he unsearchable parent Three behaviours this branch settled had no test: re-widening the `--shell` guard in `run_fallback` back over all of argv, dropping the balance requirement from `strip_outer_group`, and reading an EACCES from `metadata` as a missing program. Each passes the whole suite unchanged. `a_passthrough_keeps_its_own_shell_flag` runs a command RTK only passes through while it carries a `--shell` of its own, so a guard that reads argv beyond `rtk test` fails here. `a_group_that_closes_early_is_not_peeled` uses the `( a ) b ( c )` shape the balance check exists for. `an_unsearchable_parent_reports_exit_126` puts the program behind a directory with no search permission, where `metadata` answers EACCES without establishing whether the file is there; it leaves early under a uid that ignores the search bit, since the premise cannot hold there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(cli)!: preserve argv boundaries in generic runners
…its it parse_duration rejects a tail with no trailing `s` or no number; the filter keeps the duration on the wrapped long-run form with a warnings part, on the only-skipped path and on the quiet-mode failures line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-duration fix(pytest): preserve elapsed duration in summaries
# Conflicts: # src/discover/report.rs
…ough the walk The `_impl` tests pin the predicate but nothing reaches the `cfg!(windows)` wrapper, so a wrapper that silently stayed case-sensitive passed the suite on every runner. Add a `discover_sessions_in_projects_dir` test with a case-mismatched filter that expects a match on Windows and none elsewhere, and say in the `-p` help of `discover` and `learn` that the match is case-insensitive on Windows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A zero-session scan can come from three scopes: `--all`, `-p <filter>` and the current-directory default. The report carried a two-way flag, so a `-p` miss was described as a current-project miss and pointed at `--all` instead of at the filter. Carry the scope as an enum, print the filter or the encoded directory name and the day window in every message, and keep JSON output unchanged. Tests cover each scope, the agent notes on the zero-session path, and the JSON invariance. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brings the branch onto the current develop so CI's semgrep baseline is the develop tip rather than the July merge-base; no source changes beyond the merge itself.
…o-sessions fix(discover): distinguish zero-session scans
…h-case fix(discover): match Claude project dirs case-insensitively on Windows
…-name-4050 fix(pip): stop doubling the tool name in pip messages
…t like the sibling agents On develop the rules file handed the agent the full awareness text; the plugin bundle carried none, so a session got the rewrite and nothing that says output is condensed or how to recover with rtk proxy. The plugin now holds rules/AGENTS.md with the file awareness.level selects, plain markdown with no frontmatter, which Antigravity applies whenever the plugin is enabled. A re-run rewrites it, so changing the level takes effect. The plugin files go through the shared atomic_write, and plugin.json, the file that makes Antigravity discover the directory, is written last: a write that fails halfway leaves no plugin rather than one missing its rules. The install output tells the user to restart Antigravity, which only discovers a plugin directory on startup. Uninstall reports like Factory Droid: a "not installed (nothing to remove)" line when there is no plugin, otherwise a header and the removed items, and the dry-run footer; the helper that removes the directory prints nothing. README's agent table and the configuration guide no longer describe the rules file, and the "<1.5ms" figure goes: the hook measures about 7 ms end to end. Process-level tests pin the printed lines and that both dry runs change nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(hook): add native Google Antigravity plugin lifecycle and hook support
- Add fs::create_dir_all for .claude dir in patch_settings_json_command and run_default_mode (fixes rtk-ai#2519, rtk-ai#4046 Windows regression) - Hoist OpenCode plugin installation before Unchanged early return in run_claude_md_mode_with - Fixes rtk-ai#4046 regressions.
The previous fix created the .claude directory unconditionally, but dry-run mode must not create directories. Only create the directory when not in dry-run mode. Fixes test: test_run_default_mode_dry_run_does_not_create_missing_claude_dir
categorize_command mapped pip/pytest/ruff/mypy/sqlfluff to python but missed uv, so every rtk uv … telemetry row fell through to other. After the pip uv-fallback labelled those rows rtk pip …, the same invocation was bucketed differently depending on entry path. Adds uv to the python arm and pins the mapping in a unit test. Fixes rtk-ai#4316
Keep wrappers (sudo/env/transparent_prefixes) when asking whether the hook would have rewritten the line without RTK_DISABLED=. Peel only the RTK_DISABLED= assignments for that gate; leave gain on the syntactic strip_disabled_prefix so it does not inflate without a coverage check. Addresses KuSh round-3 CHANGES_REQUESTED on rtk-ai#3808.
… filter Successful fuzz runs collapsed to a bare "N passed", dropping the final "fuzz: elapsed:" line. Non-JSON stdout (e.g. setup errors) was discarded, so users saw "Go test: No tests found" instead of the real error.
develop moved the whole CLI out of `src/main.rs` into `src/lib.rs` behind `rtk::entry()`, so the help guard work had to be ported onto the library: `rtk_command`, `release_meta_flags`, `forwards_to_native_tool`, `try_parse_cli_from` and the k8s alias helpers now live there, and `run_cli` parses through `try_parse_cli_from` as before. Two resolutions worth naming: `RTK_META_COMMANDS` takes develop's five new entries (llm-proxy, pipeline, methods, tasks, plugins) alongside `test` and `format`. Both of the latter use trailing_var_arg, so the bad-flag loop skips them. `proxy` is now a clap alias on `Bypass` rather than a subcommand of its own, so the help test looks a node up by name or alias instead of by name alone. Verified: 4296 tests pass, clippy clean, and 24 behaviour checks hold on the merged tree. Wrapped tools answer their own `-h` (cargo, git, npm, go, docker, kubectl, pytest, vitest), RTK's own commands keep RTK's help (develop's five new ones included), and filtering is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [googleapis/release-please-action](https://github.com/googleapis/release-please-action) from 4.4.1 to 5.0.0. - [Release notes](https://github.com/googleapis/release-please-action/releases) - [Changelog](https://github.com/googleapis/release-please-action/blob/main/CHANGELOG.md) - [Commits](googleapis/release-please-action@5c625bf...45996ed) --- updated-dependencies: - dependency-name: googleapis/release-please-action dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... 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 (rtk-ai#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 rtk-ai#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>
…erde-1.0.229 chore(deps): bump serde from 1.0.228 to 1.0.229
…gnore-0.4.33 chore(deps): bump ignore from 0.4.25 to 0.4.32
…ncoding_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 (rtk-ai#4281)
…indows-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
…hich-8.0.6 chore(deps): bump which from 8.0.1 to 8.0.6
…develop/actions/upload-artifact-7 chore(deps): bump actions/upload-artifact from 4 to 7
…develop/actions/checkout-7 chore(deps): bump actions/checkout from 4 to 7
…develop/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>
…t-code-regression-test test(hook): pin exit codes across rtk rewrite (rtk-ai#3492)
Resolve the hooks README table conflict by retaining Oh My Pi and the corrected Hermes contract. No upstream workflow or Rust changes beyond the merge. Validation: fmt, workspace clippy and workspace tests pass (4,954 tests, 8 existing ignored); Hermes Python suite passes 18/18 both normally and with inherited RTK_HERMES_TIMEOUT=1e-9.
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>
…nd-work fix(hook): skip background checks in hook commands
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.
Validation only; do not merge or deploy.
Merge upstream develop at 7bff768 into the unchanged Hermes compatibility fix at be82788. This includes merged upstream rtk-ai#4578 and the upstream hook telemetry fix. No workflow or test assertions were weakened.
Candidate tree: c7806d4d121e6d650afc2a35745f66cdc162cb05.
Local formatting and diff checks passed. Python tests: 17 passed, 1 installed-flow test skipped because the local Cargo environment is unavailable. Full Rust validation is blocked by dependency network restrictions and a sandbox startup error. The owner explicitly approved this isolated draft PR to run the existing complete CI before updating the original PR branch.
The original upstream PR branch remains unchanged until this candidate passes CI.