{"slug": "claude-code-hooks-that-block-find-sed-cat-redirect-to-native-tools", "title": "Claude Code hooks that block find/sed/cat, redirect to native tools", "summary": "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.", "body_md": "A set of Claude Code `PreToolUse`\n\nhooks that deny specific Bash command\npatterns which duplicate a built-in Claude Code tool, and tell Claude to use\nthat tool instead. Each hook is a standalone script; install one, some, or\nall of them.\n\nTelling Claude in a `CLAUDE.md`\n\nfile \"don't use `find -exec`\n\n, use Grep/Glob\"\ndoesn't reliably stick — instructions are a suggestion, not an enforcement\nmechanism, and a model will still reach for a familiar shell one-liner under\nthe right prompt. It also means every Bash call built out of one of these\npatterns shows up as a permission prompt for the user to approve, when the\nnative tool call wouldn't have needed approval at all.\n\nA `PreToolUse`\n\nhook enforces it at the harness level instead: the hook runs\nbefore the Bash tool call reaches you, denies it if it matches, and hands\nClaude a reason it can act on — so it retries with the right tool rather than\njust getting stuck. Configured once in `settings.json`\n\n, it applies to every\nsession and every subagent, so you're not approving (or fixing) the same\npattern repeatedly across parallel agents.\n\nBlocks:\n\n`find . -name '*.log' -exec rm {} \\;`\n\n`find . -name '*.ts' | grep -v node_modules`\n\n`find . -type f | xargs grep -l TODO`\n\nDoesn't touch: plain `find`\n\nwith no `-exec`\n\n/piped `grep`\n\n, plain `grep`\n\n, or\n\"find\"/\"grep\" appearing as substrings of another word or inside quoted\narguments (e.g. `grep -rn \"findUser\" src`\n\n, a path like `my-find-exec-project/`\n\n).\n\nBlocks:\n\n`sed -i 's/foo/bar/' file.txt`\n\n`sed -i.bak 's/foo/bar/' file.txt`\n\n`sed -i '' 's/foo/bar/' file.txt`\n\n(BSD/macOS)`gsed -i ...`\n\n/`sed --in-place ...`\n\nDoesn't touch: `sed`\n\nwithout `-i`\n\n(printing to stdout), or `sed`\n\nused to\ntransform piped text (`git diff | sed 's/^/ /'`\n\n).\n\nBlocks:\n\n`cat notes.txt`\n\n`head -n 20 notes.txt`\n\n`tail -c 100 notes.txt`\n\n`sudo cat /etc/hosts`\n\nDoesn't touch: `tail -f`\n\n/`--follow`\n\n(streaming), `cat file1 file2`\n\n(concatenating multiple files — Read handles one file at a time), anything\npiped elsewhere (`cat notes.txt | grep TODO`\n\n, `head -n 5 file | wc -l`\n\n), and\nanything redirected to a new file (`cat notes.txt > copy.txt`\n\n).\n\nThese are text-pattern heuristics on the raw Bash command string, not a\nshell parser — deliberately simple. They won't catch every possible\nobfuscation (e.g. combined short-option clusters like `sed -ni`\n\nwhere `-i`\n\nisn't the first letter after the dash, or a quoted file path containing a\nspace in `deny-cat-head-tail.sh`\n\n's naive whitespace tokenizer), and\n`deny-find-chains.sh`\n\n/`deny-sed-inplace.sh`\n\ncan't distinguish a real\ninvocation from a command that merely *mentions* the pattern in a comment or\nstring — both get denied. If you need airtight enforcement, that's a\ndifferent, heavier tool.\n\n```\ngit clone https://github.com/<you>/claude-no-bash-detour.git\ncd claude-no-bash-detour\n./install.sh                       # installs all hooks into ~/.claude/settings.json\n./install.sh --project             # installs all hooks into ./.claude/settings.json (this repo only)\n./install.sh deny-sed-inplace      # installs only the named hook(s)\n```\n\nRequires [ jq](https://jqlang.org/). The installer copies each hook script\ninto\n\n`<config-dir>/hooks/`\n\nand adds a `Bash`\n\n-matched `PreToolUse`\n\nentry per\nhook to `settings.json`\n\n, without touching any of your existing hooks or\npermissions. It's idempotent — running it again only installs what's\nmissing. A timestamped backup of your prior `settings.json`\n\nis written\nbefore the first change.After installing, restart Claude Code (or open `/hooks`\n\nonce) to pick up the\nchange.\n\nFor each hook you want removed: delete the `PreToolUse`\n\nentry whose\n`matcher`\n\nis `\"Bash\"`\n\nand whose command points at that hook's script from\n`settings.json`\n\n, and delete the script from `~/.claude/hooks/`\n\n.\n\nEach hook script reads the hook's stdin JSON, pulls out\n`tool_input.command`\n\n, and checks it against the patterns described above. On\na match it prints a `hookSpecificOutput`\n\nJSON blob with\n`permissionDecision: \"deny\"`\n\nand a `permissionDecisionReason`\n\npointing Claude\nat the right tool; otherwise it exits silently and the command proceeds as\nnormal.\n\nMIT", "url": "https://wpnews.pro/news/claude-code-hooks-that-block-find-sed-cat-redirect-to-native-tools", "canonical_source": "https://github.com/HabibiCodeCH/claude-no-bash-detour", "published_at": "2026-07-24 08:45:50+00:00", "updated_at": "2026-07-24 08:52:17.994250+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "large-language-models"], "entities": ["Claude Code", "PreToolUse", "Bash", "jq", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/claude-code-hooks-that-block-find-sed-cat-redirect-to-native-tools", "markdown": "https://wpnews.pro/news/claude-code-hooks-that-block-find-sed-cat-redirect-to-native-tools.md", "text": "https://wpnews.pro/news/claude-code-hooks-that-block-find-sed-cat-redirect-to-native-tools.txt", "jsonld": "https://wpnews.pro/news/claude-code-hooks-that-block-find-sed-cat-redirect-to-native-tools.jsonld"}}