Skip to content

refactor: replace external hook scripts with native 'rtk hook claude' command#785

Closed
romansl wants to merge 10 commits intortk-ai:developfrom
romansl:feat/native_windows_support
Closed

refactor: replace external hook scripts with native 'rtk hook claude' command#785
romansl wants to merge 10 commits intortk-ai:developfrom
romansl:feat/native_windows_support

Conversation

@romansl
Copy link
Copy Markdown

@romansl romansl commented Mar 23, 2026

BREAKING CHANGE: External hook scripts (hooks/rtk-rewrite.sh) are no longer installed or used. Existing hook files can be safely deleted.

  • Add native 'rtk hook claude' command that reads/writes JSON directly
  • Remove hooks/rtk-rewrite.sh embedded script (no longer needed)
  • Update init.rs: install hook via 'rtk hook claude' instead of script files
  • Update hook_check.rs: native hook is always available (no file checks)
  • Remove dead code: prepare_hook_paths(), ensure_hook_installed()
  • Add cross-platform support (works identically on Windows, macOS, Linux)

Benefits:

  • No external dependencies (bash, jq)
  • Faster: no fork/exec subprocess overhead
  • Simpler installation: just 'rtk init -g', no script files

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 23, 2026

CLA assistant check
All committers have signed the CLA.

@romansl romansl changed the base branch from master to develop March 23, 2026 06:25
@romansl romansl marked this pull request as draft March 23, 2026 06:50
@pszymkowiak pszymkowiak added effort-medium 1-2 jours, quelques fichiers enhancement New feature or request labels Mar 23, 2026
@pszymkowiak
Copy link
Copy Markdown
Collaborator

[w] wshm · Automated triage by AI

📊 Automated PR Analysis

♻️ Type refactor
🟡 Risk medium

Summary

Replaces the external bash hook script (hooks/rtk-rewrite.sh) with a native 'rtk hook claude' command built into the Rust binary. This eliminates dependencies on bash and jq, removes file-based hook installation/version checking, and simplifies the setup to just 'rtk init -g' across all platforms.

Review Checklist

  • Tests present
  • Breaking change
  • Docs updated

Analyzed automatically by wshm · This is an automated analysis, not a human review.

Copy link
Copy Markdown
Collaborator

@pszymkowiak pszymkowiak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @romansl — this is the right long-term direction. Eliminating bash + jq saves ~5-10ms per hook call, which adds up over a full session.

However, the PR has 3 blocking issues and a scope concern:

P0 — Security regression

  1. Permission deny/ask rules droppedrun_claude() calls rewrite_command() but never check_command() from src/permissions.rs. Every command that should be denied or prompted is silently auto-allowed. This is a security regression shipped in PR #576.

  2. Audit logging droppedRTK_HOOK_AUDIT=1 writes to ~/.local/share/rtk/hook-audit.log are gone. Users relying on rtk hook-audit get an empty log after upgrading.

  3. Migration breaks existing installshook_already_present no longer matches rtk-rewrite.sh in settings.json. Running rtk init -g after upgrade inserts a duplicate hook entry instead of replacing the old one.

P1 — Missing wiring

  1. rtk hook claude subcommand doesn't exist in HookCommands enum — clap returns "unrecognized subcommand"
  2. hook_check::status() always returns OK — rtk gain loses the "run rtk init" diagnostic

Scope concern — should cover all agents, not just Claude Code

RTK supports 10 AI tools (Claude, Gemini, Copilot, Cursor, Windsurf, Cline, Codex, OpenCode, OpenClaw, Vibe). This PR only makes the hook native for Claude Code. If we go native for one, we should plan the same for Gemini (rtk hook gemini) and Copilot (rtk hook copilot) to avoid maintaining two architectures (bash hooks + native hooks) indefinitely.

We'd prefer a unified design:

  • rtk hook claude (this PR, once fixed)
  • rtk hook gemini (already exists, needs same native treatment)
  • rtk hook copilot (already exists, needs same native treatment)
  • All sharing the same permission check + audit log + 4-exit-code protocol

Next steps: we need to think about how to architect this properly across all agents. Are there other PRs or designs you're aware of that tackle this? We'd like to avoid fragmented efforts. Let us know if you'd like to collaborate on a broader design before reworking — we can open a design issue to align.

We'll assign @aeppling for architectural review as well. Thanks for the solid foundation!

@pszymkowiak pszymkowiak requested a review from aeppling March 26, 2026 10:14
@pszymkowiak pszymkowiak self-assigned this Mar 26, 2026
blpsoares pushed a commit to blpsoares/rtk that referenced this pull request Mar 26, 2026
The shell-based hook (rtk-rewrite.sh) requires jq to parse JSON from
Claude Code / Cursor. When jq is not installed the hook exits silently
(exit 0, no output), causing every command to fall through without RTK
rewriting — resulting in zero token savings with no visible error.

Add a soft check during `rtk init -g` that warns the user if jq is not
found on PATH, with install instructions for common platforms.

This is a stopgap until the hook is replaced by a native Rust subcommand
(see rtk-ai#785).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
blpsoares added a commit to blpsoares/rtk that referenced this pull request Mar 26, 2026
The shell-based hook (rtk-rewrite.sh) requires jq to parse JSON from
Claude Code / Cursor. When jq is not installed the hook exits silently
(exit 0, no output), causing every command to fall through without RTK
rewriting — resulting in zero token savings with no visible error.

Add a soft check during `rtk init -g` that warns the user if jq is not
found on PATH, with install instructions for common platforms.

This is a stopgap until the hook is replaced by a native Rust subcommand
(see rtk-ai#785).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aeppling
Copy link
Copy Markdown
Contributor

Hey

We are cleaning up the codebase and improving the project structure for better onboarding. As part of this effort, PR #826 reorganizes src/ from a flat layout into subfolders.

No logic changes — only file moves and import path updates.

What you need to do

Rebase your branch on develop when receiving this comment:

git fetch origin && git rebase origin/develop

Git detects renames automatically. If you get import conflicts, update the paths:

use crate::git;        // now: use crate::cmds::git::git;
use crate::tracking;   // now: use crate::core::tracking;
use crate::config;     // now: use crate::core::config;
use crate::init;       // now: use crate::hooks::init;
use crate::gain;       // now: use crate::analytics::gain;

Need help rebasing? Tag @aeppling

blpsoares added a commit to blpsoares/rtk that referenced this pull request Mar 26, 2026
The shell-based hook (rtk-rewrite.sh) requires jq to parse JSON from
Claude Code / Cursor. When jq is not installed the hook exits silently
(exit 0, no output), causing every command to fall through without RTK
rewriting — resulting in zero token savings with no visible error.

Add a soft check during `rtk init -g` that warns the user if jq is not
found on PATH, with install instructions for common platforms.

This is a stopgap until the hook is replaced by a native Rust subcommand
(see rtk-ai#785).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
romansl and others added 9 commits March 28, 2026 09:42
… command

  BREAKING CHANGE: External hook scripts (hooks/rtk-rewrite.{sh,py}) are no
  longer installed or used. Existing hook files can be safely deleted.

  - Add native 'rtk hook claude' command that reads/writes JSON directly
  - Remove hooks/rtk-rewrite.sh embedded script (no longer needed)
  - Update init.rs: install hook via 'rtk hook claude' instead of script files
  - Update hook_check.rs: native hook is always available (no file checks)
  - Remove dead code: prepare_hook_paths(), ensure_hook_installed()
  - Add cross-platform support (works identically on Windows, macOS, Linux)

  Benefits:
  - No external dependencies (Python, bash, jq)
  - Faster: no fork/exec subprocess overhead
  - Simpler installation: just 'rtk init -g', no script files

  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove external hook script checks (.sh/.py) from show_claude_config
- Remove integrity checks for deprecated hook scripts
- Check native hook via settings.json with "rtk hook claude" command
- Update hook_check test to reflect native hook behavior (always Ok)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove functions and tests that were checking for legacy .sh/.py hook files,
which are no longer used after switching to native 'rtk hook claude' command.

- Remove hook_installed_path() (checked for .sh/.py files)
- Remove parse_hook_version() (parsed version from hook scripts)
- Remove CURRENT_HOOK_VERSION constant
- Remove 6 tests for parse_hook_version()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Python hooks were only in local development branch, not in upstream.
Remove .py extension logic, keep only .sh for legacy cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After rebase, the hook_cmd::run_claude() call was missing the hooks::
prefix that other hook_cmd calls have, causing compilation failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The run_claude(), run_copilot(), and run_gemini() hook entry points
were calling rewrite_command() directly without first calling
check_command(), allowing all commands to bypass deny/ask rules.

This commit adds permission checking to all three hook functions:

- handle_vscode(): Returns early on Deny (no output), prompts on Ask
- handle_copilot_cli(): Blocks Deny rules before showing suggestions
- run_gemini(): Added print_deny() and print_ask() helpers

Also added 9 new unit tests to verify permission checking behavior.

Security issue: Commands that should be denied or require user confirmation
were silently auto-allowed by the hook system.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
During the refactor from external bash hook scripts to native
'rtk hook claude' command, the audit logging functionality was
not migrated to the Rust code.

Users relying on 'rtk hook-audit' got an empty log after upgrading.

This commit restores the audit_log() function and adds calls at all
hook entry points (run_copilot, run_claude, run_gemini, handle_vscode,
handle_copilot_cli).

Actions logged:
- rewrite: command was rewritten to rtk equivalent
- skip:no_match: no rtk equivalent found
- skip:deny_rule: deny rule matched
- skip:heredoc: heredoc detected
- skip:empty: empty input
- ask: ask rule matched

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes migration issue where running `rtk init -g` after upgrade would
create duplicate hook entries instead of replacing the old one.

Changes:
- Add legacy_hook_present() to detect old rtk-rewrite.sh hooks
- Auto-migrate old hooks to new "rtk hook claude" command
- Add 7 tests covering migration scenarios

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously always returned Ok, making the "run rtk init" diagnostic
in `rtk gain` dead code. Now inspects ~/.claude/settings.json for:
- native "rtk hook claude" hook → Ok
- legacy rtk-rewrite.sh script → Outdated
- no hook configured → Missing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@romansl romansl force-pushed the feat/native_windows_support branch from a124186 to 55a5d5b Compare March 28, 2026 08:06
@romansl
Copy link
Copy Markdown
Author

romansl commented Mar 28, 2026

I asked Claude to fix all the comments (each fix in a separate commit).

  1. rebase to dev
  2. fixed
  3. fixed
  4. fixed
  5. Claude didn't find any issues
  6. fixed

Unfortunately, I was only able to do this for Claude Code and I don't know how this will work for other agents.

The integrity module (integrity.rs) provided SHA-256 verification for
external hook scripts (~/.claude/hooks/rtk-rewrite.{sh,py}). With the
migration to native hooks (rtk hook claude command in settings.json),
this verification is no longer applicable.

Changes:
- Remove src/hooks/integrity.rs module entirely
- Move compute_hash() to trust.rs (still needed for TOML filter trust)
- Remove runtime_check() call from main.rs startup
- Remove is_operational_command() function (now unused)
- Remove remove_hash() call from init.rs uninstall flow
- Update all documentation to reference native hooks instead of scripts

Native hooks are defined directly in settings.json and don't require
separate script files, making the integrity check obsolete.

Cursor and Gemini agents still use external scripts but were never
covered by this integrity system (they live in different directories).
@romansl romansl requested a review from pszymkowiak March 28, 2026 08:37
@aeppling
Copy link
Copy Markdown
Contributor

PR #862 (by @FlorianBruniaux) implements the same goal — replacing external hook scripts with a native rtk hook claude command, and is actively maintained, MERGEABLE, and further along in review.

Thank you @romansl for the significant effort, the architectural direction you proposed is essentially what #862 is delivering.

@aeppling aeppling closed this Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-changes effort-medium 1-2 jours, quelques fichiers enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants