Skill-scoped hooks (PreToolUse/Stop) still persist after skill completes — re-report of #30874, reproducible on 2026-07-31
Summary
This is a re-report of #30874, which was auto-closed by the stale bot without a fix. I independently reproduced the exact same bug on 2026-07-31 and want to flag that it's still affecting people, including at least one other user (yuke-x68) who reported in that issue's comments that the problem also affects PostToolUse and Stop, not just PreToolUse.
Description
Hooks defined in a skill's YAML frontmatter (hooks: field) are documented as being "scoped to the component's lifecycle and only run when that component is active... cleaned up when it finishes" (hooks doc). In practice, once a skill with a hooks block is invoked, its hooks remain registered and continue firing for the rest of the session, even in turns where the skill is never invoked again.
Reproduction (2026-07-31)
- Create a throwaway skill with a
PreToolUsehook that logs a timestamp on everyBashcall:
---
name: hook-scope-test
description: test skill
user-invocable: false
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "date '+%Y-%m-%d %H:%M:%S PreToolUse fired' >> /path/to/log.txt"
---
- Invoke the skill once via the Skill tool. Confirm the hook fires on subsequent
Bashcalls within the same turn (it does — this part matches the docs). - Send a completely unrelated follow-up user message, starting a new turn, without re-invoking the skill.
- Run any
Bashcommand in that new turn.
Expected behavior
Per the docs, the hook should have been cleaned up when the skill's turn finished, so step 4's Bash call should not trigger it.
Actual behavior
The hook still fires in step 4. The log file keeps accumulating new timestamped lines for every subsequent Bash call for the rest of the session, regardless of whether the skill is invoked again.
Why this matters
This makes skill-scoped hooks unusable for their apparently intended purpose: gating tool access only while a specific skill is doing its work (e.g., a read-only audit skill that wants to disallowed-tools/hook-block writes only during its own execution). Right now, invoking such a skill once has session-wide side effects indistinguishable from putting the same hook in settings.json directly — except it's implicit and easy to forget about, since nothing in the UI indicates "this hook is still active from a skill you ran 20 turns ago."
Related
- #30874 (original report, same bug, auto-closed as stale without a fix — filing this because it's still reproducible today and per the bot's own suggestion: "Please open a new issue if this is still relevant")
4 Comments
Independently reproduced on Claude Code 2.1.229, Windows 11 (win32) — still present, and I can confirm the part that was previously only reported in a comment: all three of
PreToolUse,PostToolUse, andStopleak, not justPreToolUse.Setup
A slash-invoked skill (
/hook-test-skill) with*-matched hooks on all three events in itsSKILL.mdfrontmatter, each appending to a file:Result
The skill ran to completion (its instructions reached their final "Stop" step at turn 4). I then continued the session with unrelated work — reading files, running bash, and
WebFetch-ing the Claude Code docs. Every one of those post-skill tool calls fired the skill's hooks.Ordered
PreToolUsefiring timeline (tool name per firing):Only firings 1–4 belong to the skill's lifecycle. Firings 5–17 are all later, unrelated turns. Event totals at time of capture: PreToolUse 17, PostToolUse 16, Stop 10 — and the logs kept growing with each new tool call.
Secondary anomaly (Windows)
On Windows the
type: commandhook doesn't appear to execute as written. The command iscmd /c echo PRE TOOL FIRED >> <file>, which should append the literal line once per firing. Instead the log contains 1 literalPRE TOOL FIREDline and 18 raw hook-input JSON payloads, each preceded by aMicrosoft Windows [Version …]banner and aC:\...>prompt — as though an interactivecmdis being spawned and the hook's stdin JSON is captured into the redirect target. Possibly separate from the lifecycle bug, but it reproduced in the same run and makescmd-based hook commands behave unexpectedly on Windows.Happy to share sanitized log excerpts if useful.
Thanks for the detailed re-report and repro. I verified this on the current release (2.1.233): a
PreToolUsehook defined in a skill's frontmatter does keep firing on later turns of the same session after the skill's own turn ends — your observation is accurate.However, the current documentation now explicitly describes this as the intended behavior for skill hooks, which differ from subagent hooks:
once: trueon the hook entry.See Hooks in skills and agents. The "cleaned up when it finishes" wording you quoted matches the subagent-hooks behavior; the docs have since been clarified to spell out the skill-hooks difference.
So this is working as currently documented, and I'm going to close this as such. The underlying request — hooks scoped strictly to a skill's active lifecycle (e.g. for gating tool access only while an audit skill runs) — is a reasonable feature request; feel free to file it as an enhancement if
once: truedoesn't cover your use case.🤖 Generated with Claude Code
@bcherny we observed the behavior to be different in Claude Cowork. In there, it seemed that it would fire only for the life of the skill, and only on turns prior to the last turn. Are you able to confirm that Cowork and Claude Code observe the same behavior as far as skill-based hooks? Thanks!
Filed the enhancement request suggested above: #89669 (hook scope limited to a skill's active execution period, as distinct from
once: true).