# Install & Configure: Pre-Tool-Call Hooks: Auto-Lint Before Every File Write
## What This Is
Claude Code hooks let you intercept tool calls before and after execution. A PreToolUse hook on the Write tool can run ESLint/Prettier before Claude writes a file, ensuring every AI-generated file is already formatted — eliminating the "format the file" back-and-forth.
Source: https://github.com/anthropics/claude-code/issues/891
## Before You Start
Scan my workspace and analyze:
- The project language, framework, and directory structure
- Existing agent configuration (check for .claude/, .codex/, CLAUDE.md, settings.json, commands/, skills/ directories)
Then ask me before proceeding:
1. Which lifecycle event should this hook fire on? (PreToolUse, PostToolUse, Notification, etc.)
2. Are there any files, patterns, or tools this should be scoped to?
## What to Implement
This is an **Agent Hook** — a shell/HTTP command that fires at lifecycle events.
- Add the hook configuration to `.claude/settings.json` under the lifecycle event I specified
- If the hook needs a shell script, create it and make it executable (`chmod +x`)
- If the hook calls an external API, configure it using credentials from my .env files
- Validate the JSON config is syntactically correct before saving
## Additional Context
- Add a "hooks" section to your .claude/settings.json. The PreToolUse hook fires before any tool executes and receives the tool name and input via stdin as JSON.
- Write a shell script that receives the file path and content, runs eslint --fix and prettier --write on a temp file, then outputs the fixed content back to stdout.
- Test with a deliberately malformed file — Claude should now write clean code in one shot rather than requiring a follow-up format request.
## Reference Implementation
```
// .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/pre-write.js"
}
]
}
]
}
}
// .claude/hooks/pre-write.js
const { execSync } = require('child_process')
const input = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8'))
if (input.tool_input?.file_path?.endsWith('.ts') ||
input.tool_input?.file_path?.endsWith('.tsx')) {
// Write to temp, lint, read back
const tmp = '/tmp/claude-lint-' + Date.now() + '.ts'
require('fs').writeFileSync(tmp, input.tool_input.new_content)
try {
execSync(`npx prettier --write ${tmp}`)
input.tool_input.new_content = require('fs').readFileSync(tmp, 'utf8')
} catch(e) { /* let Claude handle it */ }
}
console.log(JSON.stringify(input))
```
## Guidelines
- Adapt everything to my existing project — do not assume a specific stack or directory layout
- Review any fetched code for safety before installing or executing it
- After setup, run a quick verification and show me a summary of exactly what was installed, where, and how to use it