Feature request : project-scoped hooks (.claude/hooks.json)

Resolved 💬 3 comments Opened Mar 16, 2026 by haytamAroui Closed Apr 30, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Global hooks in ~/.claude/settings.json fire on every project, every session, every prompt — even when the current directory has
nothing to do with the plugin that registered them.

Building AZCLAUDE (a Claude Code marketplace plugin with 691 tests), every hook script must start with guard logic:

```javascript
const goalsPath = path.join('.claude', 'memory', 'goals.md');
if (!fs.existsSync(goalsPath)) process.exit(0); // not my project, bail

This creates three real problems we hit in production:

  1. Unnecessary process spawns — Node.js starts and immediately exits on every prompt in every non-matching project
  2. Side-effect risk — before we added guards, our hooks were creating ops/observations/ directories in arbitrary user directories.

A bug in a guard means a hook runs in the wrong place.

  1. Plugin isolation — multiple plugins share the same global settings.json hooks object. Our installer had to implement per-key

merging to avoid destroying other plugins' hooks. This was flagged as a CRITICAL security finding in our own code review.

Commands (.claude/commands/) and agents (.claude/agents/) are already project-scoped. Hooks are the missing piece.

Proposed Solution

Support a .claude/hooks.json at the project root, same schema as
the hooks section of settings.json:

{
  "UserPromptSubmit": [
    { "matcher": "", "hooks": [{ "type": "command", "command": "node .claude/hooks/user-prompt.js" }] }
  ],
  "PostToolUse": [
    { "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "node .claude/hooks/post-tool-use.js" }] }
  ]
}

Merge behavior:

  1. Global hooks from ~/.claude/settings.json — fire everywhere (existing)
  2. Project hooks from .claude/hooks.json — fire only in that project (new)
  3. Both merge at runtime — no replacement, no conflict

This follows the existing pattern: .claude/commands/ and .claude/agents/
are already project-scoped. Hooks are the only feature that requires global
configuration to work per-project.

Alternative Solutions

Current workaround: Every hook checks for project markers (goals.md, CLAUDE.md) and exits early if not found. Works, but:

  • Every plugin author must implement this independently
  • Forgotten guards cause cross-project side effects (we shipped this bug)
  • Node.js still spawns on every prompt just to exit immediately

Convention-based guards: Fragile. No enforcement. Each plugin invents its own guard pattern.

Plugin-scoped hooks in marketplace format: The marketplace hooks/hooks.json exists but still registers globally via
${CLAUDE_PLUGIN_ROOT}. It doesn't scope execution to matching projects.

Priority

High - Significant impact on productivity

Feature Category

Other

Use Case Example

  1. I install AZCLAUDE via the Claude Code marketplace — it registers 3 global hooks (UserPromptSubmit, PostToolUse, Stop)
  2. I open a different project that doesn't use AZCLAUDE
  3. On every prompt, UserPromptSubmit spawns Node.js, checks for goals.md, doesn't find it, exits
  4. On every Write/Edit, PostToolUse spawns Node.js, checks for goals.md, doesn't find it, exits
  5. These are wasted process spawns that slow down every non-AZCLAUDE session

With project-scoped hooks:

  1. AZCLAUDE installs .claude/hooks.json in the project directory during /setup
  2. Hooks only fire when Claude Code is running inside that project
  3. Other projects are completely unaffected — zero process spawns, zero guards needed
  4. Multiple plugins can coexist without modifying the same global config file

Additional Context

Real-world evidence: https://github.com/haytamAroui/AZ-CLAUDE (marketplace plugin, v1.0.0, 691 tests) had to implement:

  • Per-key hook merging in the installer to avoid destroying other plugins' hooks
  • Project existence guards in all 3 hook scripts
  • Path validation to prevent side effects in non-AZCLAUDE directories

All of this complexity disappears with project-scoped hooks.

The marketplace plugin format already supports project-scoped commands and agents. Hooks are the only piece that remains
global-only.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗