Implementing a Hook
Building an .env File Guard
Let's walk through implementing a complete pre-tool use hook that prevents Claude from reading .env files. This is one of the most common and practical hook implementations -- protecting sensitive configuration from being read or searched.
// .claude/settings.local.json
{
"hooks": {
"preToolUse": [
{
"matcher": "read|grep",
"command": "node ./hooks/read_hook.js"
}
]
}
}// hooks/read_hook.js
const fs = require('fs');
// Read tool call data from stdin
const input = fs.readFileSync('/dev/stdin', 'utf8');
const data = JSON.parse(input);
// Extract the file path from tool input
// Different tools use different field names
const filePath = data.tool_input?.file_path
|| data.tool_input?.path
|| '';
// Check if the target is a .env file
if (filePath.includes('.env')) {
// stderr goes back to Claude as feedback
console.error(
`Blocked by read hook: Cannot access ${filePath}. ` +
`The .env file contains sensitive secrets.`
);
process.exit(2); // Code 2 = block the tool call
}
// All other files are allowed
process.exit(0); // Code 0 = allowRestart Required
After creating or modifying hook files, you must restart Claude Code for changes to take effect. Hooks are loaded at startup and won't be detected during an active session.
Testing Your Hook
After restarting Claude Code, test your hook by asking Claude to read the .env file. You should see Claude acknowledge that the read was blocked by your hook and offer alternative approaches.
# After restarting Claude Code:
> Read the .env file and show me the API keys
# Claude's response:
# "I attempted to read .env but it was blocked by a read hook:
# 'Cannot access .env. The .env file contains sensitive secrets.'
# I'll respect this restriction. If you need to check
# environment variables, you can review .env directly."Fallback Path Handling
Notice we check both data.tool_input.file_path and data.tool_input.path in the script. Different tools may use slightly different field names for paths, so checking multiple variants ensures your hook catches all access attempts.
Key Takeaways
- ✓Configure hooks in .claude/settings.local.json with a matcher (tool names) and command (script path)
- ✓The matcher 'read|grep' catches both file reading and content searching tools
- ✓Use console.error() to send feedback to Claude -- it becomes a message in the conversation
- ✓Check multiple path fields (file_path, path) since different tools use different field names
- ✓Always restart Claude Code after hook changes -- hooks load at startup only
Check Your Understanding
Test what you learned in this lesson.
Q1.You've created a hook script and configured it in settings.local.json, but it doesn't fire when Claude reads files. What's the most likely issue?
Q2.In the .env guard hook, why do we use exit code 2 instead of exit code 1?
Q3.What matcher value would you use to intercept both file reading and content searching?