Tooling gotcha — RTK silently corrupts some shell commands

RTK (the token-optimizing CLI proxy that auto-rewrites shell commands via a Claude Code hook) does not just filter output — for some commands it mutates the command itself before running it, producing wrong results with no obvious sign that RTK was the cause. Two failure modes confirmed: curl <url> gets the URL corrupted, and certain pipes/find-counts return empty or zeroed output.

This was hit while trying to fetch the actionlint binary during the FKITDEV-8239 review — curl failed, python3 urllib worked. It is the same family of hazard as rtk-git-log-hides-merge-commits (RTK output-filtering hides merge commits) and the long-standing RTK pipe-count mangling note in agent memory.

Symptoms (confirmed)

curl — malformed URL

curl: (3) URL using bad/illegal format or missing URL
# (libcurl: "Malformed input to a URL function")

RTK’s Rust proxy corrupts the URL string it passes through, so even a well-formed curl https://… fails with curl error (3). wget is also unreliable through the proxy.

Pipes / counts — empty or zeroed output

  • ls | sort returned empty.
  • find … | wc -l and … | grep -c … get zeroed (count comes back 0 even when matches exist).
  • A find … -o … expression came back empty during this same session.

The danger is that a zeroed count or empty listing looks like a legitimate “no results” answer — you can draw the wrong conclusion (e.g. “no file matches”) instead of realizing the command was mangled.

Git inspection commands — silently wrong answers (added 2026-08-11, FKITDEV-9197)

Four more modes, all confirmed during the CIB portal_css devel update. Every one of them returned a plausible answer rather than an error, which is what makes them dangerous:

InvocationWhat RTK didConsequence
git show <ref>:<file> | grep <pat>Garbled the output so real matches were reported as absentNearly caused a wrong conflict-resolution decision — the reviewer concluded a symbol wasn’t there when it was
diff -u <a> <b>Reformatted into an unreadable line-offset view and reported a wrong exit codeCan’t tell same-vs-different, and the exit-code fallback lies too
git diff --no-index <a> <b>Rendered as if the whole file were newEvery line looks changed; the actual delta is invisible
${PIPESTATUS[0]}BlankedAny pipeline-based pass/fail gate becomes unverifiable

(A fifth — git log -1 showing the wrong commit right after a merge commit — is the merge-filtering behaviour documented in rtk-git-log-hides-merge-commits.)

Workarounds that worked:

rtk proxy <cmd>                       # raw, unfiltered — the reliable escape hatch
git grep <pattern> <ref> -- <path>    # instead of `git show <ref>:<file> | grep`
shasum -a 256 <a> <b>                 # same-vs-different, immune to diff rendering
python3 -c "import difflib, sys; ..." # generate the diff inside python

Rule — re-verify anything you are about to assert

“The merge commit is X”, “the file does not contain Y”, “these two files are identical”, “the command exited 0” — none of these may rest on RTK-rendered output. Re-run under rtk proxy, or prove it with git grep / a hash. rtk proxy is the preferred tool here precisely because the failure mode is silent: a python bypass is fine for fetching and counting, but for a verification claim you want the raw command.

Workarounds — bypass RTK with python3

python3 is not rewritten by the hook — route the real work through it

The proxy rewrites the shell command, but a python3 one-liner runs the actual logic inside the interpreter where RTK can’t touch the URL or the pipeline.

Download a file (instead of curl/wget):

python3 -c "import urllib.request,sys; urllib.request.urlretrieve(sys.argv[1], sys.argv[2])" <url> <dest>

This is exactly how the actionlint binary was fetched after curl failed with error (3).

Run a multi-step CLI check (instead of a shell pipeline):

python3 - <<'EOF'
import subprocess
r = subprocess.run(["some-tool", "--flag", "arg"], capture_output=True, text=True)
print(r.returncode, r.stdout, r.stderr)
EOF

Use subprocess.run([...]) with an argv list (no shell=True) so the command never round-trips through the rewriting hook. This sidesteps both the URL corruption and the pipe mangling. The vault scans in this very session (find files / grep contents) were done this way after find/grep returned empty.

Counts — cross-check two independent methods: Don’t trust a single | wc -l / | grep -c. Compute the count two different ways (e.g. git ls-tree + a python len([...])) and confirm they agree before relying on the number. (See the agent-memory rtk-pipe-count-mangling note for the git-specific recipe.)

Escape hatch: rtk proxy <cmd> runs a command raw/unfiltered, but the user generally prefers agents avoid it — reach for the python bypass above first.

  • rtk-git-log-hides-merge-commits — sibling RTK gotcha: output-filtering drops merge commits from git log --graph; use %p/rev-list --parents plumbing instead.
  • RTK — the token-optimizing CLI proxy whose command rewriting causes this.
  • FKITDEV-8239 — the depcheck-CI review during which the curl-fails / python-urllib-works case was hit.
  • FKITDEV-9197 — the CIB / portal_css devel update where the four git-inspection modes above were found.
  • raiffeisen-1.9.11.100 — where the git show <rev>:<path> argument rewriting and the swallowed jest --verbose lines were first recorded.
  • jest invocations — the consolidated table, in the procedure where it bites.