cd /news/developer-tools/claude-code-hooks-that-block-find-se… · home topics developer-tools article
[ARTICLE · art-71663] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Claude Code hooks that block find/sed/cat, redirect to native tools

A set of Claude Code PreToolUse hooks blocks specific Bash command patterns that duplicate built-in Claude Code tools, such as find with -exec or piped grep, sed -i, and cat/head/tail on single files, redirecting the model to use native tools instead. The hooks enforce the restriction at the harness level, preventing permission prompts and ensuring consistent behavior across sessions and subagents. The installer, requiring jq, copies hook scripts into the config directory and adds PreToolUse entries to settings.json without affecting existing hooks.

read3 min views1 publishedJul 24, 2026
Claude Code hooks that block find/sed/cat, redirect to native tools
Image: source

A set of Claude Code PreToolUse

hooks that deny specific Bash command patterns which duplicate a built-in Claude Code tool, and tell Claude to use that tool instead. Each hook is a standalone script; install one, some, or all of them.

Telling Claude in a CLAUDE.md

file "don't use find -exec

, use Grep/Glob" doesn't reliably stick — instructions are a suggestion, not an enforcement mechanism, and a model will still reach for a familiar shell one-liner under the right prompt. It also means every Bash call built out of one of these patterns shows up as a permission prompt for the user to approve, when the native tool call wouldn't have needed approval at all.

A PreToolUse

hook enforces it at the harness level instead: the hook runs before the Bash tool call reaches you, denies it if it matches, and hands Claude a reason it can act on — so it retries with the right tool rather than just getting stuck. Configured once in settings.json

, it applies to every session and every subagent, so you're not approving (or fixing) the same pattern repeatedly across parallel agents.

Blocks:

find . -name '*.log' -exec rm {} \;

find . -name '*.ts' | grep -v node_modules

find . -type f | xargs grep -l TODO

Doesn't touch: plain find

with no -exec

/piped grep

, plain grep

, or "find"/"grep" appearing as substrings of another word or inside quoted arguments (e.g. grep -rn "findUser" src

, a path like my-find-exec-project/

).

Blocks:

sed -i 's/foo/bar/' file.txt

sed -i.bak 's/foo/bar/' file.txt

sed -i '' 's/foo/bar/' file.txt

(BSD/macOS)gsed -i ...

/sed --in-place ...

Doesn't touch: sed

without -i

(printing to stdout), or sed

used to transform piped text (git diff | sed 's/^/ /'

).

Blocks:

cat notes.txt

head -n 20 notes.txt

tail -c 100 notes.txt

sudo cat /etc/hosts

Doesn't touch: tail -f

/--follow

(streaming), cat file1 file2

(concatenating multiple files — Read handles one file at a time), anything piped elsewhere (cat notes.txt | grep TODO

, head -n 5 file | wc -l

), and anything redirected to a new file (cat notes.txt > copy.txt

).

These are text-pattern heuristics on the raw Bash command string, not a shell parser — deliberately simple. They won't catch every possible obfuscation (e.g. combined short-option clusters like sed -ni

where -i

isn't the first letter after the dash, or a quoted file path containing a space in deny-cat-head-tail.sh

's naive whitespace tokenizer), and deny-find-chains.sh

/deny-sed-inplace.sh

can't distinguish a real invocation from a command that merely mentions the pattern in a comment or string — both get denied. If you need airtight enforcement, that's a different, heavier tool.

git clone https://github.com/<you>/claude-no-bash-detour.git
cd claude-no-bash-detour
./install.sh                       # installs all hooks into ~/.claude/settings.json
./install.sh --project             # installs all hooks into ./.claude/settings.json (this repo only)
./install.sh deny-sed-inplace      # installs only the named hook(s)

Requires jq. The installer copies each hook script into

<config-dir>/hooks/

and adds a Bash

-matched PreToolUse

entry per hook to settings.json

, without touching any of your existing hooks or permissions. It's idempotent — running it again only installs what's missing. A timestamped backup of your prior settings.json

is written before the first change.After installing, restart Claude Code (or open /hooks

once) to pick up the change.

For each hook you want removed: delete the PreToolUse

entry whose matcher

is "Bash"

and whose command points at that hook's script from settings.json

, and delete the script from ~/.claude/hooks/

.

Each hook script reads the hook's stdin JSON, pulls out tool_input.command

, and checks it against the patterns described above. On a match it prints a hookSpecificOutput

JSON blob with permissionDecision: "deny"

and a permissionDecisionReason

pointing Claude at the right tool; otherwise it exits silently and the command proceeds as normal.

MIT

── more in #developer-tools 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/claude-code-hooks-th…] indexed:0 read:3min 2026-07-24 ·