Skip to content

feat(hook): native cross-platform (Windows & more) hook-rewrite command#150

Open
ayoub-khemissi wants to merge 6 commits intortk-ai:masterfrom
ayoub-khemissi:feat/native-hook-rewrite
Open

feat(hook): native cross-platform (Windows & more) hook-rewrite command#150
ayoub-khemissi wants to merge 6 commits intortk-ai:masterfrom
ayoub-khemissi:feat/native-hook-rewrite

Conversation

@ayoub-khemissi
Copy link

@ayoub-khemissi ayoub-khemissi commented Feb 16, 2026

Summary

Replace the bash-based rtk-rewrite.sh hook with a native Rust rtk hook-rewrite subcommand, eliminating the bash/jq dependency and enabling cross-platform support (Windows, macOS, Linux).

  • New src/hook_cmd.rs (1126 lines): Pure Rust command rewriter that reads Claude Code PreToolUse JSON from stdin, rewrites known commands to their rtk equivalents, and outputs updated JSON. Supports command chains (&&, ;), env var prefixes, git/cargo/docker/kubectl global flags, and 30+ command patterns (git, cargo, gh, pnpm, vitest, tsc, eslint, prettier, playwright, prisma, pytest, ruff, pip, go, golangci-lint, curl, wget, cat, grep, find, ls, tree, head, diff).
  • Updated src/init.rs: rtk init now installs the native rtk hook-rewrite command instead of the bash script. Includes migration logic to clean up old .sh hook files. Both old and new formats are detected during --uninstall.
  • Updated src/main.rs: Added hidden hook-rewrite subcommand routing to hook_cmd::run().
  • New build.rs: Set 8 MB stack size on Windows to prevent debug build stack overflow (Windows defaults to 1 MB vs 8 MB on Unix).
  • Minor src/gain.rs: Formatting fix (line wrapping for table_width calculation).

Why native?

Bash hook (rtk-rewrite.sh) Native hook (rtk hook-rewrite)
Dependencies bash + jq required None (single binary)
Windows Requires Git Bash/WSL Works natively
Performance Shell startup + jq parsing Direct Rust execution
Security Shell script attack surface Pure string manipulation, no shell spawning
Maintenance Separate script file to distribute Built into rtk binary

Security review

Per SECURITY.md checklist:

  • No Command::new("sh") — pure string rewriting, no shell spawning
  • No unsafe {} blocks
  • No network operations (reqwest::, std::net::)
  • No environment manipulation (.env("LD_PRELOAD"))
  • No .unwrap() in production paths (only in lazy_static! regex compilation)
  • Input validation via allowlists (specific subcommands matched, not denylists)
  • Graceful failure: any parse error → exit(0) (no rewrite, original command passes through)
  • init.rs changes reduce attack surface by removing bash script dependency

Test plan

  • 56 unit tests covering all rewrite patterns, edge cases, command chains, env prefixes, JSON roundtrip
  • cargo fmt --all --check && cargo clippy --all-targets && cargo test --all passes
  • Manual test: rtk init --auto-patch installs native hook command in settings.json
  • Manual test: rtk init --uninstall removes both old .sh and new native hook entries
  • Manual test: In Claude Code session, raw git status is rewritten to rtk git status
  • Cross-platform: Verify on Windows (no bash dependency needed)

Verification results (Windows 11, 2026-02-16)

Quality pipeline

Check Result
cargo fmt --all --check PASS — no diff
cargo clippy --all-targets PASS — 45 pre-existing warnings, 0 errors
cargo test --all PASS — 413 tests, 0 failures

Uninstall coverage

Scenario Result
Remove native hook (rtk hook-rewrite) from settings.json OK
Remove old bash hook (rtk-rewrite.sh) file OK
Remove RTK.md OK
Remove @RTK.md reference from CLAUDE.md OK
Migration .sh → native then uninstall OK — both cleaned up
Idempotent uninstall (nothing installed) OK — "RTK was not installed"

Hook-rewrite manual verification

Raw command Rewritten to
git status rtk git status
cargo test -- --nocapture rtk cargo test -- --nocapture
cd /project && git status && cargo test cd /project && rtk git status && rtk cargo test
TEST_ID=123 git status TEST_ID=123 rtk git status
cat src/main.rs rtk read src/main.rs
python -m pytest tests/ rtk pytest tests/
rtk git status (already prefixed) No double-rewrite
Non-Bash tool (Read) Silently ignored (exit 0)

Cross-platform (Windows)

  • Binary: Native PE64 executable, 5.4 MB stripped
  • Zero bash dependency: No bash, sh, jq, sed, grep calls in hook_cmd.rs
  • hook-rewrite: Pure Rust stdin/stdout JSON, works natively on Windows
  • Stack overflow fix: build.rs sets /STACK:8388608 (8 MB) on Windows via linker flag — zero runtime overhead, matches Unix defaults

🤖 Generated with Claude Code

ayoub-khemissi and others added 3 commits February 16, 2026 20:38
Windows default stack is 1 MB vs 8 MB on Unix. The large Commands enum
(30+ Clap variants) overflows in unoptimized debug builds. Add build.rs
with /STACK linker flag for Windows only, zero runtime overhead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep both hook-rewrite (this PR) and hook-audit (upstream rtk-ai#151) commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ayoub-khemissi ayoub-khemissi changed the title feat(hook): native cross-platform hook-rewrite command feat(hook): native cross-platform (Windows & more) hook-rewrite command Feb 17, 2026
@aeppling aeppling added the P1-critical Bloque des utilisateurs, fix ASAP label Feb 18, 2026
@aeppling
Copy link
Contributor

Hello,

Approved.

Code is correct for me, but for futures PR please split files into respective components to avoid having 1000+ lines files.

We should refactor the code structure of the project for a clean architecture that will help for easy reviewing and extensible code.

@pszymkowiak
Copy link
Collaborator

Thanks for this massive PR — the native hook-rewrite is a great step forward for cross-platform support!

I did an exhaustive test of 313 commands across all 12 categories, simulating the real Claude Code hook JSON format (tool_input.command). Here are the full results:

Test Results: 312/313 passed (99.7%)

Category Tested Passed Notes
Git (supported) 29 29 status, diff, log, add, commit, push, pull, branch, fetch, stash, show, worktree
Git (blocked) 15 15 checkout, merge, rebase, reset, cherry-pick, tag, bisect, reflog, remote, clean, blame, init, clone
Git (global flags) 7 7 -C, --no-pager, --bare, --no-optional-locks, -c key=val
GitHub CLI (supported) 17 17 pr, issue, run, api, release
GitHub CLI (blocked) 5 5 repo, gist, workflow, auth, config
Cargo (supported) 19 19 build, test, clippy, check, fmt, install, +toolchain
Cargo (blocked) 7 7 run, new, publish, doc, bench, update, add
Files 21 21 cat→read, head→read --max-lines, ls, tree, find, diff
Grep/Search 8 8 grep, rg → rtk grep
Network 5 5 curl, wget
Docker (supported) 18 18 ps, images, logs, run, build, exec, compose
Docker (blocked) 7 7 rm, rmi, stop, push, pull, network, volume
Kubectl (supported) 15 15 get, logs, describe, apply + global flags
Kubectl (blocked) 7 7 create, delete, edit, scale, exec, port-forward, rollout
JS/TS — vitest 9 9 vitest, npx vitest, pnpm vitest
JS/TS — tsc 11 11 tsc, npx tsc, pnpm tsc, vue-tsc, npx vue-tsc
JS/TS — eslint 9 8 pnpm eslint . not rewritten (see below)
JS/TS — prettier 5 5 prettier, npx prettier
JS/TS — playwright 5 5 playwright, npx playwright, pnpm playwright
JS/TS — prisma 7 7 prisma, npx prisma
NPM 6 6 npm test, npm run build/dev/lint/start
PNPM (supported) 3 3 list, ls, outdated
PNPM (blocked) 5 5 install, add, remove, run, dev
Python — pytest 8 8 pytest, python -m pytest + flags
Python — ruff (supported) 5 5 check, format
Python — ruff (blocked) 2 2 rule, linter
Python — pip/uv (supported) 8 8 list, outdated, install, show + uv variants
Python — pip/uv (blocked) 3 3 freeze, download
Go (supported) 9 9 test, build, vet + flags
Go — golangci-lint 3 3 run + flags
Go (blocked) 5 5 run, mod, install, get, generate
Env prefix 4 4 NODE_ENV=, RUST_LOG=, CI=, multi-var
Chained commands (&&) 4 4 git&&git, cargo&&cargo, cd&&ls, mkdir&&cd
Skip (no rewrite) 21 21 rtk *, echo, mkdir, cd, rm, mv, cp, chmod, which, env, export, source, python, node, make, cmake, gradle, mvn, dotnet
Heredoc 1 1 cat <<EOF skipped

Only issue: pnpm eslint . not rewritten

pnpm eslint . → no rewrite (expected rtk lint .).

The hook handles pnpm lint (script name) but not pnpm eslint (direct binary). Minor — most projects use pnpm lint in practice.

Notes on kubectl / docker compose syntax

The hook correctly mirrors real CLI syntax:

  • kubectl get podsrtk kubectl get pods
  • docker compose logs webrtk docker compose logs web

RTK currently uses shorter aliases (rtk kubectl pods, rtk docker ps/images/logs only), but that's a design issue on our side — we'll update RTK to accept the full natural syntax. No action needed on this PR.

Verdict

Excellent work. 312/313 commands rewrite correctly across 35 test categories. The chain splitting (&&, ;), env prefix preservation (NODE_ENV=test npm test), global flag stripping (git -C, --no-pager; docker -H; kubectl --namespace, -n), heredoc detection, and quote-aware parsing all work correctly.

The only fix needed is adding pnpm eslint support alongside the existing pnpm lint handling. Thanks!

I'm giving @aeppling the green light to merge once the pnpm eslint fix is in.

Split the monolithic 1127-line hook_cmd.rs into 8 focused modules under
src/hook/ for better maintainability and code review:

- mod.rs: entry point, chain parsing, orchestrator
- git.rs: git + GitHub CLI rewriters
- cargo.rs: cargo rewriter
- files.rs: file ops (cat, grep, ls, tree, find, diff, head) + network
- js_ts.rs: JS/TS tooling (vitest, tsc, eslint, prettier, playwright, prisma, pnpm)
- containers.rs: Docker + kubectl rewriters
- python.rs: pytest, ruff, pip/uv rewriters
- go.rs: go test/build/vet + golangci-lint rewriters
- helpers.rs: shared utility functions (replace_prefix, starts_with_any)

All 71 hook tests passing. No behavioral changes.
- Update version references from 0.18.1 to 0.19.0 in README.md,
  CLAUDE.md, and ARCHITECTURE.md
- Update module count in ARCHITECTURE.md (47 → 49)
- Update validate-docs workflow and script to check native hook module
  (src/hook/) instead of deprecated bash script (.claude/hooks/rtk-rewrite.sh)
@ayoub-khemissi
Copy link
Author

Fixes from review feedback

1. Split hook_cmd.rs into modules

The monolithic 1127-line file has been split into 9 files under src/hook/:

File Responsibility
mod.rs Entry point, chain parsing, orchestrator
git.rs Git + GitHub CLI
cargo.rs Cargo
files.rs cat, grep, ls, tree, find, diff, head, curl, wget
js_ts.rs vitest, tsc, eslint, prettier, playwright, prisma, pnpm
containers.rs Docker + kubectl
python.rs pytest, ruff, pip/uv
go.rs go test/build/vet, golangci-lint
helpers.rs Shared utilities (replace_prefix, starts_with_any)

All 71 hook tests preserved and passing.

2. Fix pnpm eslint (the 1/313 missing)

Added pnpm eslint .rtk lint . support in js_ts.rs, with 2 dedicated tests (test_pnpm_eslint, test_pnpm_eslint_bare).

313/313 commands now covered.

3. Fix CI validate-docs

  • Updated version 0.18.10.19.0 in README.md, CLAUDE.md, ARCHITECTURE.md
  • Fixed module count 4749 in ARCHITECTURE.md
  • Updated workflow and validate-docs script to check src/hook/ (native hook) instead of .claude/hooks/rtk-rewrite.sh (legacy bash hook)

- Take upstream version 0.20.1 for docs
- Update module count to 51 (upstream additions + hook split)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1-critical Bloque des utilisateurs, fix ASAP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments