Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Select hook scope (plugin, project, global) by audience. Use when authoring a hook.
.claude/skills/athola-hook-scope-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 9% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 69% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 79% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 118% | 0% |
This skill helps you choose the right location for Claude Code hooks based on their purpose, audience, and persistence needs.
abstract:hook-authoring)abstract:hooks-eval)> hooks/hooks.json is automatically loaded by Claude Code when the plugin is enabled. > Do NOT add "hooks": "./hooks/hooks.json" to your plugin.json - this causes duplicate load errors. > The hooks field in plugin.json is only needed for additional hook files beyond the standard hooks/hooks.json.
| Scope | Location | Audience | Committed? | Persistence | |-------|----------|----------|------------|-------------| | Plugin | hooks/hooks.json in plugin | Plugin users | With plugin | When plugin enabled | | Project | .claude/settings.json | Team members | Yes (repo) | Per project | | Global | ~/.claude/settings.json | Only you | Never | All sessions |
Only plugin users → Plugin hooks
All team members on this project → Project hooks
/src/production/ configsOnly me, everywhere → Global hooks
Yes, as part of a distributable plugin → Plugin hooks Yes, shared with team in repo → Project hooks No, keep private → Global hooks
Only when my plugin is active → Plugin hooks Always in this specific project → Project hooks Always, in every project I work on → Global hooks
Location: <plugin-root>/hooks/hooks.json
When to use:
Configuration:
json{ "PreToolUse": [ { "matcher": "Read", "hooks": [{ "type": "command", "command": "echo \"Plugin reading: $(jq -r '.tool_input.file_path')\" >> ${CLAUDE_PLUGIN_ROOT}/log.txt" }] } ] }
> Note: Use string matchers ("Read") not object matchers ({"toolName": "Read"}).
Key features:
${CLAUDE_PLUGIN_ROOT} for plugin-relative pathsExamples:
Location: .claude/settings.json (in project root)
When to use:
Configuration:
json{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [{ "type": "command", "command": "cmd=$(jq -r '.tool_input.command // empty'); if [[ \"$cmd\" == *\"production\"* ]]; then echo 'BLOCKED: Production access requires approval'; exit 1; fi" }] } ] } }
> Note: Use string matchers ("Bash") not object matchers.
Key features:
Examples:
Location: ~/.claude/settings.json
When to use:
Configuration:
json{ "hooks": { "PreToolUse": [ { "hooks": [{ "type": "command", "command": "echo \"$(date): $(jq -r '.tool_name')\" >> ~/.claude/audit.log" }] } ] } }
Key features:
Examples:
Claude Code loads settings in this priority (highest first):
claude --flag).claude/settings.local.json).claude/settings.json)~/.claude/settings.json)Important: Multiple hooks from different scopes can respond to the same event. When they do, all matching hooks execute in parallel.
Is this hook part of a plugin's core functionality?
├─ YES → Plugin hooks (hooks/hooks.json in plugin)
└─ NO ↓
Should all team members on this project have this hook?
├─ YES → Project hooks (.claude/settings.json)
└─ NO ↓
Should this hook apply to all my Claude sessions?
├─ YES → Global hooks (~/.claude/settings.json)
└─ NO → Reconsider if you need a hook at allPlugin hooks:
Project hooks:
Global hooks:
SessionStart hooks now receive additional input fields via stdin:
| Field | Type | Description | |-------|------|-------------| | session_id | string | Unique session identifier | | source | enum | "startup" \| "resume" \| "clear" \| "compact" | | agent_type | string | Agent name if --agent flag used, empty otherwise |
The agent_type field enables scope-appropriate context injection:
python# Skip heavy context for review agents input_data = json.loads(sys.stdin.read()) if input_data.get("agent_type") in ["code-reviewer", "quick-query"]: print(json.dumps({"hookSpecificOutput": {"additionalContext": "Minimal"}}))
This is particularly useful for:
least two of the three decision questions (audience, version control, persistence).
hooks/hooks.json, .claude/settings.json, or~/.claude/settings.json) is confirmed to exist or is created at the correct path.
"hooks": "./hooks/hooks.json" to plugin.json (duplicate-loadguard); this absence is verified before the hook is deployed.
on this machine.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→pass | 18,623 | 7,930 | -57% | 1 | 1 | 0% | 3,265 | 3,555 | +9% | 0 | 0 | — |
case-01 | fail→fail | 16,601 | 6,716 | -60% | 1 | 1 | 0% | 2,791 | 3,378 | +21% | 0 | 0 | — |
case-03 | fail→pass | 11,771 | 6,591 | -44% | 1 | 1 | 0% | 1,987 | 3,352 | +69% | 0 | 0 | — |
case-04 | fail→fail | 13,995 | 10,689 | -24% | 1 | 1 | 0% | 2,544 | 4,188 | +65% | 0 | 0 | — |
case-05 | fail→fail | 19,466 | 8,878 | -54% | 1 | 1 | 0% | 1,400 | 3,640 | +160% | 0 | 0 | — |
case-06 | fail→pass | 17,935 | 9,561 | -47% | 1 | 1 | 0% | 3,052 | 3,747 | +23% | 0 | 0 | — |
case-07 | fail→pass | 9,632 | 4,281 | -56% | 1 | 1 | 0% | 1,622 | 2,899 | +79% | 0 | 0 | — |
case-08 | fail→pass | 7,563 | 3,787 | -50% | 1 | 1 | 0% | 1,253 | 2,737 | +118% | 0 | 0 | — |
case-09 | fail→pass | 10,524 | 2,973 | -72% | 1 | 1 | 0% | 1,797 | 2,648 | +47% | 0 | 0 | — |
case-10 | fail→pass | 12,844 | 6,528 | -49% | 1 | 1 | 0% | 1,999 | 3,198 | +60% | 0 | 0 | — |
case-11 | fail→pass | 12,352 | 3,454 | -72% | 1 | 1 | 0% | 2,225 | 2,756 | +24% | 0 | 0 | — |
case-12 | pass→pass | 12,043 | 7,116 | -41% | 1 | 1 | 0% | 1,850 | 3,147 | +70% | 0 | 0 | — |
case-13 | fail→pass | 10,404 | 4,592 | -56% | 1 | 1 | 0% | 1,664 | 2,933 | +76% | 0 | 0 | — |
case-14 | pass→pass | 9,622 | 4,802 | -50% | 1 | 1 | 0% | 1,443 | 2,914 | +102% | 0 | 0 | — |
case-15 | pass→pass | 5,786 | 3,702 | -36% | 1 | 1 | 0% | 939 | 2,781 | +196% | 0 | 0 | — |
case-16 | fail→pass | 4,834 | 3,423 | -29% | 1 | 1 | 0% | 787 | 2,739 | +248% | 0 | 0 | — |
case-22 | fail→pass | 8,855 | 2,974 | -66% | 1 | 1 | 0% | 1,345 | 2,691 | +100% | 0 | 0 | — |
case-17 | fail→pass | 10,940 | 3,069 | -72% | 1 | 1 | 0% | 1,946 | 2,690 | +38% | 0 | 0 | — |
case-18 | fail→pass | 18,616 | 8,638 | -54% | 1 | 1 | 0% | 2,905 | 3,613 | +24% | 0 | 0 | — |
case-19 | fail→pass | 17,567 | 5,658 | -68% | 1 | 1 | 0% | 2,893 | 3,132 | +8% | 0 | 0 | — |
case-20 | fail→pass | 15,518 | 7,063 | -54% | 1 | 1 | 0% | 2,335 | 3,439 | +47% | 0 | 0 | — |
case-21 | fail→pass | 12,409 | 4,793 | -61% | 1 | 1 | 0% | 1,810 | 2,972 | +64% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +73 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.