Three Claude Code hooks that warn when a session starts burning tokens on the same anti-patterns. The hooks don't block anything — they inject a system message that the model sees and usually self-corrects on the next turn (delegating to a sub-agent, switching to Edit instead of re-reading, etc).
Tested on macOS only. Should work on Linux (uses
bash,jq). Windows is not supported.
| Hook | Triggers when | Hint it gives |
|---|---|---|
token-watch-read.sh |
The same file is Read 3+ times in one session |
Suggests keeping the file's structure in context and using Edit without re-reading |
token-watch-bash.sh |
3+ recursive searches per session (grep -r, find ... -name, rg ...) |
Suggests delegating the search to an Explore sub-agent |
token-watch-webfetch.sh |
3+ WebFetch calls per session |
Suggests delegating browsing to a general-purpose sub-agent |
On a Claude subscription (Pro/Max), the 5-hour rate limit is consumed by tokens, and tokens are consumed by everything that lands in the conversation context — files, search results, fetched pages, history.
Three patterns disproportionately blow up context:
- Re-reading the same file — each
Readships the whole file again, even if it's already in cache. - Recursive
grep/findfrom the main thread — every match line ends up in the main context. - Long sequences of
WebFetch— each fetched page lands in the main context as a wall of HTML/markdown.
These hooks count occurrences per session and, after the third hit, post a system message. The model treats system messages as instructions, so on the next turn it usually self-corrects without you having to interrupt and remind it.
- Claude Code with hooks support
bashjq—brew install jq(macOS) orapt install jq(Linux)
git clone https://github.com/itchernetski/claude-code-token-watch.git
cp claude-code-token-watch/hooks/token-watch-*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/token-watch-*.shMerge the contents of settings.example.json into your existing ~/.claude/settings.json. If you don't have hooks configured yet, the file from this repo is a complete working example.
If you already have a PreToolUse array, add these three entries to it:
{
"matcher": "Read",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/token-watch-read.sh" }]
},
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/token-watch-bash.sh" }]
},
{
"matcher": "WebFetch",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/token-watch-webfetch.sh" }]
}Open the /hooks menu inside Claude Code (which re-reads the config) or restart the CLI. Existing sessions don't pick up new hooks automatically.
Each hook receives the full hook input JSON on stdin (with session_id, tool_name, tool_input). It appends a record to a per-session log under /tmp/claude-token-watch/<session_id>/ and counts how many times the same pattern has occurred. On the third occurrence and beyond, it prints a JSON object with a systemMessage field — Claude Code displays this to the user and forwards it to the model on the next turn.
State is isolated by session_id, so parallel sessions in different projects don't interfere with each other. The OS cleans /tmp/ on its own.
The default warning threshold is 3 occurrences per session. To make it stricter (warn on the 2nd) or looser (warn on the 5th), edit the magic number in each script:
if [ "$TOTAL" -ge 3 ]; then # ← change hereOut of the box, the warning messages are in Russian. To translate, edit the string inside the --arg msg "..." argument in each script. The message structure is the same in all three.
Comment out or remove its entry from ~/.claude/settings.json and reload via /hooks.
Set "disableAllHooks": true at the top level of your settings file. Note this disables every hook, including the ones from this repo and any others you have configured.
The cleanest test is to trigger the third occurrence yourself:
# Simulate three Reads of the same file
for i in 1 2 3; do
echo '{"session_id":"test","tool_name":"Read","tool_input":{"file_path":"/tmp/foo.py"}}' \
| ~/.claude/hooks/token-watch-read.sh
doneThe third invocation should print a JSON {"systemMessage": "..."} to stdout. If you see a JSON output, the hook works. If not, check that jq is installed and the script is executable.
To verify the hook fires inside an actual Claude Code session, ask Claude to read the same file three times in a row. After the third read, the warning will appear inline in the transcript.
MIT