Follow-up to #2688
PR #2713 (shipped in v0.8.15) restructured the after_/before_ hook dispatch in core command SKILLs with a ## Mandatory Post-Execution Hooks H2 + directive language + ## Done When checklist. Empirically, the emission half works: the agent now reliably emits
EXECUTE_COMMAND: my-extension.my-hook-target
in its response stream during /speckit-plan invocations on v0.8.15+.
The execution half does not work in pure Claude Code (no specify-CLI wrapper). Nothing in Claude Code's slash-command runtime watches the agent's stdout for EXECUTE_COMMAND: lines and dispatches them. The agent emits the directive, waits ~6 seconds, and returns control without ever invoking the underlying hook script. The result is functionally identical to #2688's original failure mode (the registered hook does not run) — just with a more visible symptom (the directive shows in the agent's output, so the user can SEE that something should have happened).
Reproduction
The repro is structural and doesn't depend on any particular extension. A minimal stub is enough:
specify init . --integration claude on spec-kit ≥ 0.8.15.
- Hand-create a minimal local extension at
.specify/extensions/repro/:
extension.yml:
schema_version: "1.0"
extension:
id: repro
name: "Hook Dispatch Repro"
version: "0.0.1"
requires:
speckit_version: ">=0.8.15"
provides:
commands:
- name: repro.before-plan
file: commands/before-plan.md
description: "Hook dispatch repro target."
hooks:
before_plan:
command: repro.before-plan
optional: false
description: "Should run before /speckit-plan."
commands/before-plan.md: any content; the command's body never runs in this repro so the contents don't matter beyond being present.
- Run
specify extension add --dev .specify/extensions/repro so the hook is registered in .specify/extensions.yml and a SKILL is generated under .claude/skills/repro-before-plan/.
- From inside Claude Code (NOT via
specify run or any CLI wrapper), invoke /speckit-plan against any feature directory.
- Observe: the agent emits the H2 block with
EXECUTE_COMMAND: repro.before-plan correctly, then stalls. The hook command body never runs. /speckit-plan then proceeds (or fails, depending on what the hook was supposed to do) as if no hook were registered.
Empirical log grep on a real session against a before_plan hook that's supposed to write a file:
grep -E 'EXECUTE_COMMAND|tool_dispatch_start tool=Bash.*<hook-script>' debug.log
# → emits EXECUTE_COMMAND line, zero matching Bash dispatches
Why this is distinct from #2104
#2104 asked for auto-run as a new feature and was closed because the workflow engine is the right answer for deterministic dispatch. This issue is narrower: the existing best-effort dispatch contract documented in PR #2713 doesn't actually dispatch in the most common Claude Code invocation pattern. I am not asking for auto-run; I am asking that the contract the SKILL describes actually completes in the documented happy path.
Hypothesis
The v0.8.15 SKILL implicitly assumes a platform-level stdout watcher that parses EXECUTE_COMMAND: lines. That watcher exists when specify is the orchestrator (auto_execute_hooks: true in .specify/extensions.yml is presumably the flag that activates it). It does not exist when the agent's slash command is invoked directly in Claude Code, which is the default invocation pattern for ~all extension users.
Proposed Resolution Paths (one of)
-
Strengthen the SKILL contract. Add an explicit instruction immediately after the EXECUTE_COMMAND: directive emission: "You MUST then invoke the Bash tool with the underlying hook script path to actually execute the command. Emitting the directive alone is not sufficient — the directive is a ceremony signal, not a dispatcher." This is the lowest-cost fix and makes the contract self-contained.
-
Document the prerequisite explicitly. Add a note to extensions/EXTENSION-API-REFERENCE.md that EXECUTE_COMMAND: directives require an orchestrator process (e.g. specify run) and will not be dispatched by pure Claude Code slash invocations. Extension authors can then advertise the dispatch limitation in their READMEs.
-
Ship a Claude Code hook. Bundle a small settings.json hook (CLAUDE_CODE_HOOKS_DIR) that watches assistant message text for EXECUTE_COMMAND: <command> lines and dispatches them via the Bash tool — closing the gap at the platform level without depending on the agent doing two things.
(1) is the cleanest from an extension-author perspective. (3) is the most robust but adds non-trivial machinery.
Component
Specify CLI (commands, templates) / Extensions / Agent Integration
AI Agent (if applicable)
Claude Code (Claude Opus 4.7) — but pattern is structural; same failure mode is likely with Cursor / Codex / Gemini CLI when invoked directly without a spec-kit CLI orchestrator.
Use Cases
Every extension that registers an after_* or before_* hook with optional: false and expects it to fire during agent-direct slash invocations. Concrete classes:
References
Follow-up to #2688
PR #2713 (shipped in v0.8.15) restructured the after_/before_ hook dispatch in core command SKILLs with a
## Mandatory Post-Execution HooksH2 + directive language +## Done Whenchecklist. Empirically, the emission half works: the agent now reliably emitsin its response stream during
/speckit-planinvocations on v0.8.15+.The execution half does not work in pure Claude Code (no
specify-CLI wrapper). Nothing in Claude Code's slash-command runtime watches the agent's stdout forEXECUTE_COMMAND:lines and dispatches them. The agent emits the directive, waits ~6 seconds, and returns control without ever invoking the underlying hook script. The result is functionally identical to #2688's original failure mode (the registered hook does not run) — just with a more visible symptom (the directive shows in the agent's output, so the user can SEE that something should have happened).Reproduction
The repro is structural and doesn't depend on any particular extension. A minimal stub is enough:
specify init . --integration claudeon spec-kit ≥ 0.8.15..specify/extensions/repro/:extension.yml:commands/before-plan.md: any content; the command's body never runs in this repro so the contents don't matter beyond being present.specify extension add --dev .specify/extensions/reproso the hook is registered in.specify/extensions.ymland a SKILL is generated under.claude/skills/repro-before-plan/.specify runor any CLI wrapper), invoke/speckit-planagainst any feature directory.EXECUTE_COMMAND: repro.before-plancorrectly, then stalls. The hook command body never runs./speckit-planthen proceeds (or fails, depending on what the hook was supposed to do) as if no hook were registered.Empirical log grep on a real session against a
before_planhook that's supposed to write a file:Why this is distinct from #2104
#2104 asked for auto-run as a new feature and was closed because the workflow engine is the right answer for deterministic dispatch. This issue is narrower: the existing best-effort dispatch contract documented in PR #2713 doesn't actually dispatch in the most common Claude Code invocation pattern. I am not asking for auto-run; I am asking that the contract the SKILL describes actually completes in the documented happy path.
Hypothesis
The v0.8.15 SKILL implicitly assumes a platform-level stdout watcher that parses
EXECUTE_COMMAND:lines. That watcher exists whenspecifyis the orchestrator (auto_execute_hooks: truein.specify/extensions.ymlis presumably the flag that activates it). It does not exist when the agent's slash command is invoked directly in Claude Code, which is the default invocation pattern for ~all extension users.Proposed Resolution Paths (one of)
Strengthen the SKILL contract. Add an explicit instruction immediately after the
EXECUTE_COMMAND:directive emission: "You MUST then invoke the Bash tool with the underlying hook script path to actually execute the command. Emitting the directive alone is not sufficient — the directive is a ceremony signal, not a dispatcher." This is the lowest-cost fix and makes the contract self-contained.Document the prerequisite explicitly. Add a note to
extensions/EXTENSION-API-REFERENCE.mdthatEXECUTE_COMMAND:directives require an orchestrator process (e.g.specify run) and will not be dispatched by pure Claude Code slash invocations. Extension authors can then advertise the dispatch limitation in their READMEs.Ship a Claude Code hook. Bundle a small
settings.jsonhook (CLAUDE_CODE_HOOKS_DIR) that watches assistant message text forEXECUTE_COMMAND: <command>lines and dispatches them via the Bash tool — closing the gap at the platform level without depending on the agent doing two things.(1) is the cleanest from an extension-author perspective. (3) is the most robust but adds non-trivial machinery.
Component
Specify CLI (commands, templates) / Extensions / Agent Integration
AI Agent (if applicable)
Claude Code (Claude Opus 4.7) — but pattern is structural; same failure mode is likely with Cursor / Codex / Gemini CLI when invoked directly without a spec-kit CLI orchestrator.
Use Cases
Every extension that registers an
after_*orbefore_*hook withoptional: falseand expects it to fire during agent-direct slash invocations. Concrete classes:before_planmaterializes a transientspec.mdfrom a non-default spec store, so/speckit-planhas something to read; without dispatch,/speckit-planhas nothing to read and silently bails.References