[BUG] Skill-scoped hooks defined in SKILL.md frontmatter are not triggered within plugins
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?
Skill-scoped hooks defined in SKILL.md frontmatter (using the hooks YAML property) are not being triggered when the skill is loaded within a plugin.
I created a test skill with a PreToolUse hook that should trigger when Bash tool is called. The hook is defined in the SKILL.md frontmatter:
---
name: test
description: This skill should be used when the user says "test hooks"...
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "touch /tmp/skill-hook-triggered.txt"
once: true
---
When I invoke the skill and then use the Bash tool, the hook is never executed - the file /tmp/skill-hook-triggered.txt is never created.
What Should Happen?
When a skill is loaded (via /test or natural language trigger), the hooks defined in that skill's SKILL.md frontmatter should be registered and executed when matching tools are used.
In this case, after loading the test skill and running any Bash command, the file /tmp/skill-hook-triggered.txt should be created.
Steps to Reproduce
- Create a plugin with the following structure:
````
plugins/eval/
├── .claude-plugin/
│ └── plugin.json
└── skills/
└── test/
└── SKILL.md
- Create
SKILL.mdwith hooks in frontmatter:
```yaml
---
name: test
description: This skill should be used when the user says "test hooks", "hook 테스트", or wants to verify that skill-level hooks are working.
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "touch /tmp/skill-hook-triggered.txt"
once: true
---
# Test Skill
Hook 동작 테스트를 위한 간단한 스킬입니다.
```
- Register and enable the plugin in
settings.json
- Invoke the skill by typing
/testor "hook 테스트"
- Run any Bash command (e.g.,
ls)
- Check if
/tmp/skill-hook-triggered.txtexists
Result: File does not exist - hook was not triggered.
Claude Model
- Opus
Is this a regression?
- I don't know
Claude Code Version
2.1.5 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Cursor
Additional Information
The skill loads and displays correctly (the SKILL.md content is shown to the model), but the hooks defined in the frontmatter appear to be ignored.
This may be related to how skill-scoped hooks are parsed and registered when loaded dynamically via the skill invocation flow vs. static plugin hooks defined in hooks.json.
Plugin structure used for testing:
// .claude-plugin/plugin.json
{
"name": "eval",
"description": "Evaluation framework plugin",
"version": "1.1.0"
}
23 Comments
This is quite painful for us, as all of our shared Claude Code infra in the team is shared via plugins. :-/
Note that the same bug exists also for agents, not just skills.
We also tested whether this is related to how plugins are loaded:
--plugin-dir): hooks broken/plugin install): hooks brokenSo the bug affects all plugin components regardless of installation method.
Summary:
Same problem here.
How is that not fixed yet?
Root Cause Analysis
I traced through the CLI source code (v2.1.34) and found the exact root cause. The bug also affects agent-scoped hooks defined in plugin agent
.mdfrontmatter, not just skill-scoped hooks.The Problem
There are two different functions that load agent definitions from
.mdfiles:dI2— loads plugin agents (fromplugins/*/agents/*.md)iH5— loads local agents (from.claude/agents/*.md)iH5(local agents) correctly parses and includes hooks:dI2(plugin agents) completely omits hooks:The frontmatter IS parsed (via
QD), soX.hooksexists on the frontmatter object, butcH5()is never called and hooks are never included in the returned agent definition.Why hooks never fire
Later, in the subagent spawn handler (
cvasync generator):Since
A.hooksis alwaysundefinedfor plugin agents,Rd2(which registers hooks into sessionHooks) is never called.The fix
In
dI2, addcH5()parsing and include hooks in the returned object:Additional note
dI2is also missing several other frontmatter fields thatiH5supports:disallowedTools,mcpServers,permissionMode,forkContext, andmaxTurns. These likely have the same issue — they're parsed from frontmatter for local agents but ignored for plugin agents.Verified with
.claude/agents/*.md) fire correctlyplugins/*/agents/*.md) never fire🤖 Generated with Claude Code
(Is this based on some reverse engineering attempts, @Butanium? Cool.)
Honestly, I'm surprised it's not a P2 or higher. Hooks are critical for determinism. They simply don't work in plugins for agent and skills.
Plugins are the primary delivery vehicle for us—and I'm sure many other teams. We're basically stuck. It's incredibly frustrating. It shows a big Q/A gap and that composability of the various features isn't cleanly implemented.
Yeah Claude is just very good at reverse engineering. At this point I'm considering working on my own reversed engineer fork to fix all the annoying bugs like this one
@Butanium this reverse engineering is amazing!! 😁
The problem is still not fixed in 2.1.37. I spent the whole day messing around with this.
The same issue in 2.1.39.
Found the same in reddit.
@dicksontsai - any chance you could weigh in on this one?
Still an issue on 2.1.62.
Still an issue on 2.1.63.
While no one at @anthropics may be paying attention to this bug, leaving it unaddressed is a clear reason to switch to the Codex CLI. For one thing, opus-4.6's ability to follow instructions is clearly worse than gpt-5.3's. Hook-based control is essential to guide Opus correctly, otherwise it would be difficult to put into production. (Humans would have to keep cleaning up after them.)
Currently, our company is debating whether we should primarily use Codex or Claude. I love Claude, but if the issue isn't resolved in the next patch version, we'll definitely switch to Codex. I hope a human reads this comment.
The biggest issue, after all, is that Claude Code is not open source.
I understand the business decision to keep it obfuscated for the sake of competitiveness, but that only works until a competitor surpasses it in quality. After that, the opposite force starts to work against you.
@kuguma same here. Our entire team switched to pi because of this very bug remaining unaddressed for so long.
It's still not fixed in the latest release, we are also blocked because of this.
@mavam is it fixed for you?
No. I've actually abandoned Claude Code as CLI tool because of this bug fest. I'm now a happy
piuser where I have full control about any type of hook.The way Anthropic implements hooks in skills is also non-standard. They should have been in
metadatafrom day one. That's what I'm doing now.Also have been grappling with this.
pls fix!
It's becoming a joke :D
Fwiw this is my crappy workaround that symlink on your current .claude the stuff from your plugin. Current limitation is that new skill files don't get added automatically you need to update manually with --force
Project-context agents now also affected — Windows, 2026-05-18
The summary table in the OP (Project skill / Project agent = Work, Plugin = Broken) doesn't match observed behavior anymore. As of today on Windows + Claude Code Desktop, Project agent (
.claude/agents/*.md) frontmatter hooks also fail to fire. Posting fresh reproduction; closely matches #18392 (locked, dup of this issue) / #19213 (locked) / #39468.Setup
.claude/agents/parallel-evaluator.md:probe-posttool.shemitshookSpecificOutput.additionalContextJSON viajq, so injection would be observable.Observation
When the subagent is spawned via Agent tool (
subagent_type=parallel-evaluator):UserPromptSubmitconfigured output → not delivered to subagent contextSessionStart(any matcher) → not deliveredPostToolUse:Bashprobe marker → not delivered, even when the subagent uses the Bash toolNotably, Claude Code host-side
<system-reminder>blocks (MCP server instructions, "work without stopping for clarifying questions") DO appear in subagent context after tool use. So the subagent's reminder-injection channel is operational — user-configured hook output just doesn't enter it. (May be related to #43612'sCLAUDE_CODE_SIMPLE/_R()observation.)Regression timeline anchor
A downstream project relying on subagent + project-agent + hook integration was implemented and end-to-end verified working on 2026-03-26. The same setup no longer works as of 2026-05-18. Estimated window: ~7 weeks. Possibly correlated with
v2.1.105(PreCompact hook blocking),v2.1.118(mcp_tool in hooks), orv2.1.133(effort level passed to hooks) — not narrowed further.Additional observation
.claude/agents/*.mdmodifications are not picked up mid-session — Claude Code Desktop restart is required for the agent registry to refresh.Environment
.claude/agents/*.md), not plugin