Master Claude Code Hooks: PreToolUse & PostToolUse for Secure AI Workflows
This guide explains how to use Claude Code hooks—PreToolUse and PostToolUse—to run custom commands before or after tool execution, configure them in global or project settings, and implement practical examples such as blocking .env file access, type‑checking TypeScript, and preventing duplicate queries.
Understanding Claude Code Hooks
Hooks let you execute commands automatically before ( PreToolUse) or after ( PostToolUse) Claude attempts to run a tool, enabling automation such as code formatting, test execution, or restricting access to sensitive files.
Hook Mechanics
When you ask Claude a question, the query and any tool definition are sent to the Claude model. The model may decide to invoke a tool, which is then executed by Claude Code and the result returned. A hook inserts custom code into this pipeline, running immediately before or after the tool call.
There are two hook types:
PreToolUse hook – runs before the tool is executed.
PostToolUse hook – runs after the tool has finished.
Hook Configuration
Hooks are defined in Claude configuration files. You can place them in a global file ( ~/.claude/settings.json) that affects all projects, in a project‑shared file ( .claude/settings.json), or in a personal file ( .claude/settings.local.json) that is not committed.
Hooks can be added manually to these JSON files or created with the /hooks command inside Claude Code.
PreToolUse Hook Example
The following JSON shows a PreToolUse hook that runs a Node command whenever the Read tool is about to be used:
{
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "node /home/hooks/read_hook.ts"
}
]
}
]
}The command receives a JSON payload on stdin containing details such as session_id, tool_name, and tool_input. The script can allow the operation, block it (by exiting with code 2), or provide an error message.
PostToolUse Hook Example
A PostToolUse hook can run after a write, edit, or multi‑edit operation. Example configuration:
{
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node /home/hooks/edit_hook.ts"
}
]
}
]
}Since the tool has already run, the hook cannot stop the action but can perform follow‑up tasks such as formatting the edited file or sending feedback to Claude.
Practical Use Cases
Code formatting after Claude edits a file.
Running tests automatically when files change.
Blocking Claude from reading or editing specific files (e.g., .env).
Logging which files Claude accessed.
Validating naming conventions or coding standards.
Key Points
Hooks let you extend Claude Code by integrating your own tools and workflows. PreToolUse hooks control what Claude can do, while PostToolUse hooks enhance the work Claude has already performed.
Defining a Hook to Block Sensitive Files
The example project includes a .env file containing a secret key. To prevent Claude from reading it, we create a PreToolUse hook that matches the Read and Grep tools and checks the requested file path.
{
"matcher": "Read|Grep",
"command": "node ./hooks/read_hook.js"
}The Node script ( read_hook.js) reads the JSON payload, extracts tool_input.file_path, and aborts with exit code 2 if the path contains .env:
async function main() {
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
const toolArgs = JSON.parse(Buffer.concat(chunks).toString());
const readPath = toolArgs.tool_input?.file_path || toolArgs.tool_input?.path || "";
if (readPath.includes('.env')) {
console.error("You cannot read the .env file");
process.exit(2);
}
}
main();After saving the configuration and script, restart Claude Code and attempt to read .env. Claude receives the error message and reports that the operation was blocked.
Additional Useful Hooks
TypeScript type‑check hook – a PostToolUse hook that runs tsc --noEmit after every file edit, feeding any type errors back to Claude.
Duplicate query detection hook – a PreToolUse hook that spawns a second Claude instance to scan the ./queries directory for existing similar queries before creating a new one.
Hook Debugging Tips
Because the JSON payload varies by hook type, you can add a temporary PostToolUse hook that writes the raw input to a file (e.g., jq . > post‑log.json) to inspect the exact structure.
Claude Code SDK Overview
The Claude Code SDK lets you programmatically run Claude Code from TypeScript, Python, or the CLI. It shares the same tool set and permissions as the CLI version, with read‑only access by default. You can enable write permissions by specifying allowedTools in the query options or by adjusting the project‑level settings.
for await (const message of query({
prompt,
options: { allowedTools: ["Edit"] }
})) {
console.log(JSON.stringify(message, null, 2));
}Typical SDK use cases include automated code reviews, CI/CD quality checks, documentation generation, and any workflow that benefits from AI‑driven analysis.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
