Skip to content

Instantly share code, notes, and snippets.

@AlCalzone
Created March 12, 2026 09:55
Show Gist options
  • Select an option

  • Save AlCalzone/8000c84e0b398cefee7ff8b208bba987 to your computer and use it in GitHub Desktop.

Select an option

Save AlCalzone/8000c84e0b398cefee7ff8b208bba987 to your computer and use it in GitHub Desktop.
Prevent Claude from using bash commands over its built-in tools
#!/usr/bin/env bash
# ~/.claude/hooks/enforce-builtin-tools.sh
#
# PreToolUse hook: blocks file-inspection shell commands and redirects
# Claude to use the appropriate built-in Claude Code tool instead.
#
# Install:
# mkdir -p ~/.claude/hooks
# cp enforce-builtin-tools.sh ~/.claude/hooks/
# chmod +x ~/.claude/hooks/enforce-builtin-tools.sh
#
# Then add the hook config to ~/.claude/settings.json (see settings.json).
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Exit early if no command was found in the input
if [[ -z "$COMMAND" ]]; then
exit 0
fi
# Strip leading whitespace and get the first token (the binary being called)
TRIMMED=$(echo "$COMMAND" | sed 's/^[[:space:]]*//')
BINARY=$(echo "$TRIMMED" | awk '{print $1}')
# Helper: deny with a message sent back to Claude via stderr
deny() {
echo "$1" >&2
exit 2
}
case "$BINARY" in
find)
deny "Use the Glob tool to find files by pattern (e.g. '**/*.ts'), or the Grep tool to search by content. Do not use the 'find' shell command."
;;
grep|rg|ag|ack)
deny "Use the Grep tool to search file contents. Do not use '${BINARY}' as a shell command."
;;
ls|dir)
deny "Use the LS tool to list directory contents. Do not use '${BINARY}' as a shell command."
;;
cat)
deny "Use the Read tool to read file contents. Do not use 'cat'."
;;
head|tail)
deny "Use the Read tool to read file contents (it supports line offsets). Do not use '${BINARY}'."
;;
less|more|bat)
deny "Use the Read tool to read file contents. Do not use '${BINARY}'."
;;
sed)
# Allow sed when used in a pipeline (likely transforming output, not reading files directly).
# Block it when it's the first command and appears to be reading a file.
if echo "$COMMAND" | grep -qE '(^|\|)\s*sed\s.+\s\S+\.[a-zA-Z]+'; then
deny "Use the Read tool to read file contents, and the Edit or MultiEdit tool to make changes. Do not use 'sed' to read or modify files directly."
fi
;;
awk)
# Same heuristic as sed: block when it looks like awk is reading a file directly.
if echo "$COMMAND" | grep -qE '(^|\|)\s*awk\s.+\s\S+\.[a-zA-Z]+'; then
deny "Use the Read tool to read file contents. Do not use 'awk' to read files directly."
fi
;;
wc)
# wc -l on a file is often used as a substitute for knowing file size before reading.
# Allow wc in pipelines, block direct file reads.
if echo "$COMMAND" | grep -qE '^wc\s'; then
deny "Use the Read tool to inspect file contents. Do not use 'wc' to read files directly."
fi
;;
esac
# All other Bash commands are allowed
exit 0
{
// other settings
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/enforce-builtin-tools.sh"
}
]
}
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment