[BUG] Plugin-registered WorktreeCreate hooks in hooks/hooks.json are never dispatched — settings.json hooks for the same event fire normally (2.1.101)
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— onlyPLUGIN_SessionStartandPLUGIN_UserPromptSubmit. NoPLUGIN_WorktreeCreate.settings.log— all three entries includingSETTINGS_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/Removehooks not called in Claude Desktop. Different surface (Desktop vs CLI), and does not distinguish plugin-registered vs settings-registered hooks. - #36205 — In-session
EnterWorktreetool ignoresWorktreeCreate/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.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗