What Are Hooks?
Claude Code hooks are scripts that intercept tool execution at specific lifecycle points. RTK uses the PreToolUse hook to modify Bash commands before they reach the shell.
Hook lifecycle : Claude decides to run a command → PreToolUse hook intercepts → Hook rewrites command → Modified command executes → Claude receives output
Claude Code types: git status
│
▼
┌────────────────────────────────┐
│ ~/.claude/settings.json │
│ PreToolUse hook registered │
└────────────────────────────────┘
│
▼
┌────────────────────────────────┐
│ rtk-rewrite.sh │
│ Input: "git status" │
│ Output: "rtk git status" │ ← Transparent rewrite
└────────────────────────────────┘
│
▼
┌────────────────────────────────┐
│ RTK (Rust binary) │
│ Executes: git status │
│ Filters output │
└────────────────────────────────┘
│
▼
Claude receives: "3 modified, 1 untracked ✓"
↑ not 50 lines of raw git output
The rewrite is transparent : Claude never knows the command was modified. It just sees optimized output.
Auto-Rewrite Hook Architecture
RTK’s hook script (rtk-rewrite.sh) is a Bash script that:
Receives : JSON input from Claude Code with the original command
Parses : Extracts the command string
Matches : Pattern-matches against known RTK commands
Rewrites : Transforms to rtk <cmd> equivalent
Returns : JSON output with updatedInput field
If no match is found, the hook returns empty JSON (command passes through unchanged).
Hook Script Structure
#!/bin/bash
# Guards: check dependencies before set -euo pipefail
if ! command -v rtk & > /dev/null || ! command -v jq & > /dev/null; then
exit 0 # Silently skip if dependencies missing
fi
set -euo pipefail
INPUT = $( cat )
CMD = $( echo " $INPUT " | jq -r '.tool_input.command // empty' )
# Skip if empty or already using rtk
if [ -z " $CMD " ] || [[ " $CMD " =~ ^rtk \ ]]; then
exit 0
fi
# Pattern matching and rewriting...
REWRITTEN = ""
if echo " $CMD " | grep -qE '^git[[:space:]]+status' ; then
REWRITTEN = $( echo " $CMD " | sed 's/^git status/rtk git status/' )
elif echo " $CMD " | grep -qE '^cargo[[:space:]]+test' ; then
REWRITTEN = $( echo " $CMD " | sed 's/^cargo test/rtk cargo test/' )
# ... more patterns
fi
# Return JSON with updated command
if [ -n " $REWRITTEN " ]; then
jq -n --arg cmd " $REWRITTEN " '{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {"command": $cmd}
}
}'
fi
Guards and Safety
The hook includes guard clauses to handle edge cases:
Dependency check : Exits silently if rtk or jq not found
Empty command : Skips if no command string
Already rewritten : Skips if command already starts with rtk
Heredocs : Skips commands with << (shell syntax incompatible with rewriting)
Guards must appear before set -euo pipefail. Otherwise, missing dependencies cause the hook to crash instead of failing gracefully.
How the Hook Works
1. Command Detection
The hook pattern-matches the first word of the command:
# Matches:
git status
git log -10
cargo test --release
# Does NOT match (already using rtk):
rtk git status
rtk cargo test
2. Environment Variable Preservation
Commands with environment variable prefixes are handled correctly:
# Input:
TEST_SESSION_ID = 2 npx playwright test
# Output:
TEST_SESSION_ID = 2 rtk playwright test
The hook strips env vars for pattern matching, then re-adds them after rewriting.
3. Command Chaining
The hook only rewrites the first command in chains:
# Input:
git add . && git commit -m "fix" && git push
# Output:
rtk git add . && rtk git commit -m "fix" && rtk git push
Each command in the chain is rewritten independently because Claude Code invokes the hook once per command.
4. Passthrough Commands
Commands without RTK support pass through unchanged:
# Input:
echo "hello"
# Output:
echo "hello" # No rewrite
RTK’s passthrough support means unhandled commands still work.
Commands Rewritten
The hook rewrites the following commands:
Version Control
Raw Command Rewritten To git status/diff/log/add/commit/push/pull/branch/fetch/stash/showrtk git ...gh pr/issue/run/api/releasertk gh ...
Build & Test
Raw Command Rewritten To cargo test/build/clippy/check/install/nextest/fmtrtk cargo ...go test/build/vetrtk go ...golangci-lintrtk golangci-lint
JavaScript/TypeScript
Raw Command Rewritten To vitest / pnpm testrtk vitest runtsc / vue-tscrtk tsceslint / pnpm lintrtk lintprettierrtk prettierplaywrightrtk playwrightprismartk prismanpm test / npm run <script>rtk npm ...pnpm list/ls/outdatedrtk pnpm ...
Python
Raw Command Rewritten To pytest / python -m pytestrtk pytestruff check/formatrtk ruff ...pip list/outdated/install/showrtk pip ...uv pip ...rtk pip ...mypy / python -m mypyrtk mypy
Files & Search
Raw Command Rewritten To cat <file>rtk read <file>rg/grep <pattern>rtk grep <pattern>lsrtk lstreertk treefind <pattern>rtk find <pattern>diffrtk diffhead -N <file>rtk read <file> --max-lines N
Containers
Raw Command Rewritten To docker ps/images/logs/run/build/exec/composertk docker ...kubectl get/logs/describe/applyrtk kubectl ...
Network
Raw Command Rewritten To curlrtk curlwgetrtk wget
Infrastructure
Raw Command Rewritten To awsrtk awspsqlrtk psql
Commands already using rtk, heredocs (<<), and unrecognized commands pass through unchanged.
Installation
Automatic (Recommended)
The hook installs automatically with:
This creates:
~/.claude/hooks/rtk-rewrite.sh (executable, with guards)
Entry in ~/.claude/settings.json
See Claude Code Integration for full walkthrough.
Manual Installation
If automatic setup fails:
Copy hook script
# From RTK source repo:
cp .claude/hooks/rtk-rewrite.sh ~/.claude/hooks/rtk-rewrite.sh
chmod +x ~/.claude/hooks/rtk-rewrite.sh
Edit settings.json
Add to ~/.claude/settings.json: {
"hooks" : {
"PreToolUse" : [
{
"matcher" : "Bash" ,
"hooks" : [
{
"type" : "command" ,
"command" : "~/.claude/hooks/rtk-rewrite.sh"
}
]
}
]
}
}
Restart Claude Code
Close and reopen Claude Code, then test: git status # Should show compact output
Per-Project Installation
For project-specific hooks, create .claude/hooks/rtk-rewrite.sh in your project root and reference it in .claude/settings.json:
{
"hooks" : {
"PreToolUse" : [
{
"matcher" : "Bash" ,
"hooks" : [
{
"type" : "command" ,
"command" : "./.claude/hooks/rtk-rewrite.sh"
}
]
}
]
}
}
Hook Debugging
Enable Audit Logging
Track every hook invocation:
Logs are written to ~/.local/share/rtk/hook-audit.log:
2026-03-05T14:23:45Z | rewrite | git status | rtk git status
2026-03-05T14:24:10Z | skip:already_rtk | rtk cargo test | -
2026-03-05T14:24:35Z | skip:no_match | echo hello | -
Columns:
Timestamp (UTC)
Action (rewrite, skip:*)
Original command
Rewritten command (or - if skipped)
Custom Audit Directory
export RTK_AUDIT_DIR = "/tmp/rtk-audit"
Verify Hook Execution
Check if the hook is being invoked:
# In Claude Code conversation:
git status
# Then check audit log:
tail -1 ~/.local/share/rtk/hook-audit.log
Expected:
2026-03-05T14:30:00Z | rewrite | git status | rtk git status
Verify Hook Permissions
ls -l ~/.claude/hooks/rtk-rewrite.sh
Should show -rwxr-xr-x (executable).
If not executable:
chmod +x ~/.claude/hooks/rtk-rewrite.sh
Test Hook Manually
Run the hook script directly:
echo '{"tool_input": {"command": "git status"}}' | ~/.claude/hooks/rtk-rewrite.sh
Expected output:
{
"hookSpecificOutput" : {
"hookEventName" : "PreToolUse" ,
"permissionDecision" : "allow" ,
"updatedInput" : {
"command" : "rtk git status"
}
}
}
Alternative: Suggest Hook
If you prefer Claude Code to suggest rtk usage rather than automatically rewriting, use the suggest hook pattern.
Auto-Rewrite vs Suggest
Aspect Auto-Rewrite Hook Suggest Hook Strategy Modifies command before execution Emits system reminder when rtk-compatible command detected Effect Claude never sees original command Claude receives hint to use rtk Adoption 100% (forced) ~70-85% (depends on adherence) Use Case Production, guaranteed savings Learning mode, auditing Overhead Zero (transparent) Minimal (reminder in context)
When to Use Suggest
You want to audit which commands Claude chooses to run
You’re learning rtk patterns and want visibility
You prefer explicit decisions over transparent rewrites
You want to preserve exact command execution for debugging
Suggest Hook Setup
Create suggest hook
mkdir -p ~/.claude/hooks
cp .claude/hooks/rtk-suggest.sh ~/.claude/hooks/rtk-suggest.sh
chmod +x ~/.claude/hooks/rtk-suggest.sh
Register in settings.json
{
"hooks" : {
"PreToolUse" : [
{
"matcher" : "Bash" ,
"hooks" : [
{
"type" : "command" ,
"command" : "~/.claude/hooks/rtk-suggest.sh"
}
]
}
]
}
}
Restart Claude Code
Test with any rtk-compatible command. Claude will receive a system reminder but execute the original command.
The suggest hook outputs systemMessage instead of updatedInput, informing Claude that an rtk alternative exists without modifying the command.
Troubleshooting
Hook Not Rewriting Commands
Symptom : Commands show raw output after hook installation.
Checks :
Verify hook is registered:
cat ~/.claude/settings.json | grep rtk-rewrite
Verify hook is executable:
test -x ~/.claude/hooks/rtk-rewrite.sh && echo "Executable" || echo "Not executable"
Check dependencies:
command -v rtk && command -v jq
Enable audit logging:
Then run a command and check logs.
Hook Crashes
Symptom : Claude Code shows “Hook execution failed” error.
Common causes :
set -euo pipefail runs before guards → Move guards to top of script
Missing jq dependency → Install: brew install jq / apt install jq
Invalid JSON output → Test hook manually with echo '{...}' | hook.sh
Hook Ignores Certain Commands
Symptom : Some commands rewrite, others don’t.
Cause : Pattern doesn’t match command syntax.
Example : npm run test vs npm test
Check the hook script patterns at ~/.claude/hooks/rtk-rewrite.sh around line 130-140 for npm handling.
Next Steps
Configuration Customize RTK behavior and settings
Claude Code Setup Full installation walkthrough