Skip to content

fix(grep): keep rg on the ripgrep path — split the rg/grep rewrite rule#2460

Open
alex-mextner wants to merge 1 commit into
rtk-ai:developfrom
alex-mextner:fix/rewrite-rg-stays-on-ripgrep-path
Open

fix(grep): keep rg on the ripgrep path — split the rg/grep rewrite rule#2460
alex-mextner wants to merge 1 commit into
rtk-ai:developfrom
alex-mextner:fix/rewrite-rg-stays-on-ripgrep-path

Conversation

@alex-mextner

Copy link
Copy Markdown

Summary

The single rewrite rule ^(rg|grep)\s+ in src/discover/rules.rs collapses both
tools onto rtk grep. rtk grep runs ripgrep as its backend but applies
grep-flavored argument translation (src/cmds/system/grep_cmd.rs): it strips
-r/-R/--recursive, rewrites BRE \| to PCRE |, defaults empty paths to .,
and forwards only a curated allowlist of rg flags. That translation is correct for
grep, but it silently corrupts genuine ripgrep invocations:

  • ripgrep-only flags (--hidden, -g/--glob, --stats, -uu, -P, --json)
    are dropped or rejected;
  • the short-flag collision is decisive — -r is recursive in grep but --replace
    in ripgrep, so a grep-flavored wrapper that strips -r changes what rg means;
  • path-less rg PATTERN (ripgrep recurses CWD) is mishandled.

This PR keeps each tool on its own backend by splitting the one rule into two:
grep keeps routing to rtk grep (unchanged), and rg now routes to rtk rg.
rtk rg is intentionally not a Clap subcommand, so it falls through
run_fallback in main.rs, which executes the real rg binary with every argument
forwarded verbatim (still metrics-tracked). No crossover in either direction;
ripgrep semantics stay faithful.

This is the minimal, conflict-free version of what the larger #2183 attempted.
It does not change grep's backend and adds no new filtered subcommand, so it avoids
the conflicts/pushback #2183 hit while still resolving the rg correctness issues.
A real filtered rtk rg subcommand can come later as a separate, additive change.

Changes

src/discover/rules.rs

  • Split the ^(rg|grep)\s+ rule into ^rg\s+rtk rg and ^grep\s+rtk grep.
  • The new rg rule carries savings_pct: 0.0 because rtk rg is a verbatim
    passthrough (no output filtering). Keeping the inherited 75% would make
    rtk discover falsely report rg as a high-value missed optimization.

src/discover/registry.rs

  • Updated test_rewrite_rg_pattern to expect rtk rg (was rtk grep).
  • Added tests: rg and grep each route to their own target; ripgrep-only flags,
    flag ordering, the -r=--replace case, and path-less rg PATTERN are forwarded
    verbatim to rtk rg; classify reports rtk rg at 0% and rtk grep at 75%.

Docs (README.md, docs/usage/FEATURES.md)

  • Updated the auto-rewrite table and the rtk grep section so rg is documented as
    rewriting to rtk rg (verbatim ripgrep passthrough), not rtk grep.

Issues fixed

Issue Title
#1824 rg auto-rewrite to rtk grep breaks normal ripgrep flag ordering (--glob, -g, --version)
#2167 rtk grep calls system grep instead of ripgrep — breaks -g/--glob flags
#2301 rtk hook claude rewrites rgrtk grep so path-less ripgrep searches silently return 0 matches / hang on stdin
#2338 rtk rewrite should keep rg on the ripgrep path

Known limitations / follow-ups (out of scope here)

rtk rg is intentionally not a Clap subcommand — it relies on run_fallback,
which is the generic path for any unknown rtk <cmd>. That path has two pre-existing
warts that now also apply to the (newly intentional) rg route:

  1. When a .rtk/filters.toml exists and is untrusted, run_fallback prints
    [rtk] WARNING: untrusted project filters … (and runs TOML-filter lookup) before
    the ripgrep output — so it is not byte-for-byte clean passthrough in that case.
  2. A successful fallback run records record_parse_failure_silent(.., true), so
    legitimate rg usage shows up under rtk gain --failures as a parse failure.

These are broader run_fallback behaviors (true for every unknown command today), not
needed to fix the rg correctness bugs this PR targets. The minimal, safe follow-up is
to make declared passthrough tools first-class — e.g. special-case args[0] == "rg" in
run_fallback to skip the TOML/trust-warning path and the parse-failure accounting — or
add a real filtered rtk rg subcommand (the #2183 direction). I kept that out of this PR
to stay minimal and conflict-free; happy to send it as a focused follow-up.

Relation to #2183

#2183 ("respect the invoked tool — grep runs grep, rg runs ripgrep") is still OPEN
and CONFLICTING. It took a bigger approach: a real filtered rtk rg subcommand and
switching rtk grep to system grep. This PR does not supersede that design wholesale —
it lands the smallest slice that fixes the open rg issues (the rewrite-rule split)
without touching grep's backend or adding a subcommand, so it rebases cleanly on the
current develop and stays in scope. If maintainers prefer #2183's larger direction,
this can be closed; otherwise this unblocks the rg correctness issues now.

Test plan

  • cargo fmt --all -- --check — clean
  • cargo clippy --all-targets -- -D warnings — clean
  • cargo test --all — 2198 passed (the 2 local failures in
    hooks::rewrite_cmd::tests::unattestable_passthrough::{test_plain_command_still_rewrites, test_fd_dup_redirect_still_rewrites} are pre-existing on develop and depend on the
    developer's local Claude Code permission allow-list for git status; with a clean
    $HOME — as on CI — all of them pass, and they are unrelated to this diff)
  • Manual testing (built binary):
    rtk rewrite "rg --hidden -g '*.rs' --stats foo"  ->  rtk rg --hidden -g '*.rs' --stats foo
    rtk rewrite "rg -r 'NEW' pattern"                ->  rtk rg -r 'NEW' pattern
    rtk rewrite "rg TODO"                            ->  rtk rg TODO
    rtk rewrite "grep -rn foo src/"                  ->  rtk grep -rn foo src/
    rtk rg --glob '*.rs' 'pub fn run' src/discover/  ->  runs real ripgrep, matches (was: grep: unrecognized option --glob)
    

The single `^(rg|grep)\s+` rule routed BOTH tools to `rtk grep`, whose
argument translation is grep-flavored: it strips `-r`/`-R`/`--recursive`,
rewrites BRE `\|` to PCRE `|`, defaults empty paths to `.`, and forwards
only a curated allowlist of rg flags. That silently corrupts genuine
ripgrep invocations:

- ripgrep-only flags (`--hidden`, `-g`/`--glob`, `--stats`, `-uu`, `-P`,
  `--json`) are dropped or rejected;
- the short-flag collision `-r` (grep recursive vs ripgrep `--replace`)
  changes intent;
- path-less `rg PATTERN` (ripgrep recurses CWD) is mishandled.

Split the rule in two: `grep` keeps routing to `rtk grep` (unchanged),
`rg` now routes to `rtk rg`. `rtk rg` is not a Clap subcommand, so it
falls through `run_fallback` in `main.rs`, which executes the real `rg`
binary with every argument forwarded verbatim (still metrics-tracked).
This keeps ripgrep semantics faithful with no crossover in either
direction.

`rtk rg` does no output filtering, so its rule carries `savings_pct: 0.0`.
Docs (rewrite table, savings table, `rtk grep` section) and the Claude
hook rewrite regression test are updated so `rg` is documented and tested
as rewriting to `rtk rg`, not `rtk grep`.

Closes rtk-ai#1824, rtk-ai#2167, rtk-ai#2301, rtk-ai#2338.

Signed-off-by: Alex Ultra <invntrm@icloud.com>
@alex-mextner alex-mextner force-pushed the fix/rewrite-rg-stays-on-ripgrep-path branch from 76a1234 to 00c5c0f Compare June 16, 2026 10:46
@CLAassistant

CLAassistant commented Jun 16, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@alex-mextner alex-mextner marked this pull request as ready for review June 16, 2026 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants