[BUG] Plugin-registered WorktreeCreate hooks in hooks/hooks.json are never dispatched — settings.json hooks for the same event fire normally (2.1.101)

Resolved 💬 5 comments Opened Apr 11, 2026 by yun-sangho Closed Jun 19, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

WorktreeCreate (and WorktreeRemove) hooks registered by a plugin via hooks/hooks.json are never dispatched on Claude Code 2.1.101 when a session is started with --worktree.

The same plugin's SessionStart and UserPromptSubmit hooks fire normally, proving the plugin is loaded and the plugin hook pipeline is otherwise working. The same WorktreeCreate command, registered in a settings file loaded via --settings, does fire. Claude Code silently falls back to its built-in git worktree add path and creates the worktree under .claude/worktrees/<name>/.

| Event | Plugin (hooks/hooks.json) | Settings (settings.json) |
|--------------------|:---------------------------:|:--------------------------:|
| WorktreeCreate | ❌ not dispatched | ✅ fires |
| SessionStart | ✅ fires | ✅ fires |
| UserPromptSubmit | ✅ fires | ✅ fires |

Net effect: any plugin that relies on WorktreeCreate / WorktreeRemove (env-file mirroring, dependency install, secret setup, …) is inert on 2.1.101, even though the plugin is installed, enabled, its manifest validates, and its other hook events fire normally.

What Should Happen?

Plugin-registered WorktreeCreate and WorktreeRemove hooks (hooks/hooks.json) should be dispatched the same way settings.json WorktreeCreate / WorktreeRemove hooks are. Either both fire or neither — currently only the settings-registered dispatch path handles the event, and the plugin-registered one is silently skipped.

Error Messages/Logs

$ cat /tmp/r/plugin.log
PLUGIN_SessionStart
PLUGIN_UserPromptSubmit
# note: no PLUGIN_WorktreeCreate line

$ cat /tmp/r/settings.log
SETTINGS_WorktreeCreate
SETTINGS_SessionStart
SETTINGS_UserPromptSubmit

$ git -C /tmp/r/repo worktree list
/tmp/r/repo                            ae4a37a [master]
/tmp/r/repo/.claude/worktrees/foo      ae4a37a [worktree-foo]
# worktree was created via Claude Code's built-in fallback path,
# not by the plugin hook (which never ran).

$ claude plugin validate /tmp/r/p
Validating plugin manifest: /tmp/r/p/.claude-plugin/plugin.json
Validating hooks: /tmp/r/p/hooks/hooks.json
✔ Validation passed with warnings

Steps to Reproduce

Minimal reproduction — creates a one-file plugin whose hooks/hooks.json registers WorktreeCreate, SessionStart and UserPromptSubmit, plus a parallel settings.json that registers the same three events, then runs claude --worktree.

mkdir -p /tmp/r/p/.claude-plugin /tmp/r/p/hooks /tmp/r/repo

cat > /tmp/r/p/.claude-plugin/plugin.json <<'JSON'
{"name":"wt-repro","version":"0.0.1"}
JSON

cat > /tmp/r/p/hooks/hooks.json <<'JSON'
{"hooks":{
  "WorktreeCreate":   [{"hooks":[{"type":"command","command":"echo PLUGIN_WorktreeCreate   >> /tmp/r/plugin.log"}]}],
  "SessionStart":     [{"hooks":[{"type":"command","command":"echo PLUGIN_SessionStart     >> /tmp/r/plugin.log"}]}],
  "UserPromptSubmit": [{"hooks":[{"type":"command","command":"echo PLUGIN_UserPromptSubmit >> /tmp/r/plugin.log"}]}]
}}
JSON

cat > /tmp/r/settings.json <<'JSON'
{"hooks":{
  "WorktreeCreate":   [{"matcher":"","hooks":[{"type":"command","command":"echo SETTINGS_WorktreeCreate >> /tmp/r/settings.log; echo \"$CLAUDE_PROJECT_DIR/.claude/worktrees/foo\""}]}],
  "SessionStart":     [{"matcher":"","hooks":[{"type":"command","command":"echo SETTINGS_SessionStart     >> /tmp/r/settings.log"}]}],
  "UserPromptSubmit": [{"matcher":"","hooks":[{"type":"command","command":"echo SETTINGS_UserPromptSubmit >> /tmp/r/settings.log"}]}]
}}
JSON

cd /tmp/r/repo && git init -q && git -c commit.gpgsign=false commit --allow-empty -m init -q

claude \
  --plugin-dir /tmp/r/p \
  --settings   /tmp/r/settings.json \
  --worktree   foo \
  -p "exit"

echo '--- plugin.log ---';   cat /tmp/r/plugin.log
echo '--- settings.log ---'; cat /tmp/r/settings.log

Observed:

  • plugin.log — only PLUGIN_SessionStart and PLUGIN_UserPromptSubmit. No PLUGIN_WorktreeCreate.
  • settings.log — all three entries including SETTINGS_WorktreeCreate.
  • git worktree list — shows the worktree at .claude/worktrees/foo. Claude Code created it via its built-in fallback path after the plugin hook was silently skipped.

Positive control: running the same subprocess without --worktree shows that PLUGIN_SessionStart and PLUGIN_UserPromptSubmit both fire from the plugin. The plugin is fully loaded and its hook pipeline is working for every event except WorktreeCreate / WorktreeRemove. claude plugin validate /tmp/r/p also passes.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.101 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Circumstantial evidence from the native binary

Grepping /opt/claude-code/bin/claude (2.1.101) for arrays of hook event names returns two plausible allowlists, neither of which contains WorktreeCreate / WorktreeRemove:

"PreToolUse","PostToolUse","PostToolUseFailure","Notification","UserPromptSubmit","SessionStart","SessionEnd","Stop"
"PreToolUse","PostToolUse","Notification","UserPromptSubmit","SessionStart","SessionEnd","Stop","SubagentStop"

For comparison, the binary contains 76 literal occurrences of WorktreeCreate and 49 of WorktreeRemove elsewhere in the code, so the events are clearly known to the runtime — they just appear to be missing from whichever allowlist / switch governs plugin-registered hook dispatch. Consistent with (but not proof of) the hypothesis that the plugin hook dispatcher uses an allowlist which was never extended to include the worktree events.

Downstream user report

An independently filed report with matching symptoms (macOS, Darwin 25.4.0 arm64, Claude Code 2.1.101) is here: https://github.com/yun-sangho/sccm/issues/9 — in that report the user added top-of-file fs.appendFileSync diagnostic calls to all three on-disk copies of the plugin script (~/.claude/plugins/cache/.../worktree-create.js, the previous cached version, and the marketplace copy) and none of them fired. That rules out cache-lookup / path-resolution issues and points squarely at the dispatch layer.

Related — probably not duplicates

  • #29716 — WorktreeCreate / Remove hooks not called in Claude Desktop. Different surface (Desktop vs CLI), and does not distinguish plugin-registered vs settings-registered hooks.
  • #36205 — In-session EnterWorktree tool ignores WorktreeCreate / Remove. Different surface (mid-session tool vs CLI --worktree), settings-registered hooks only.
  • #27276 — Docs missing WorktreeCreate / WorktreeRemove. Docs issue, not runtime.

This report is specifically about plugin-registered hooks on the CLI --worktree surface, which to my knowledge is not covered by any existing issue.

Suggested fix direction

Add WorktreeCreate and WorktreeRemove to whichever allowlist / switch currently governs plugin-registered hook dispatch, so that plugin hooks/hooks.json entries for these events are forwarded to the same dispatcher that already handles settings.json entries.

View original on GitHub ↗

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