Skip to content

fix(fp-check): scope completeness hooks to skill/agent frontmatter (#143)#188

Open
dguido wants to merge 1 commit into
mainfrom
fix/fp-check-scope-hooks-to-skill
Open

fix(fp-check): scope completeness hooks to skill/agent frontmatter (#143)#188
dguido wants to merge 1 commit into
mainfrom
fix/fp-check-scope-hooks-to-skill

Conversation

@dguido

@dguido dguido commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #143. Supersedes #165 (which is based on stale 1.0.0 code and conflicts with main).

The fp-check plugin ships plugins/fp-check/hooks/hooks.json registering a Stop hook and a SubagentStop hook, both with "matcher": "*". Plugin-level hooks are always-on, so both fire on every (sub)agent stop in any session where fp-check is merely installed — burning a ~30s LLM turn and injecting stop-feedback even when fp-check was never used, only to no-op via the prompt's own fallback.

Why not just delete the Stop hook (the approach in #165)?

#165 deletes the top-level Stop hook and keeps SubagentStop. That is a half-fix:

  • It leaves the identical bug in SubagentStop — which also uses matcher: "*" and also relies on its prompt to no-op, so it keeps firing a full LLM turn on every subagent stop in unrelated sessions.
  • It loses real coverage. The Stop hook checks the overall verdict assembly (Phase 5's devil's-advocate questions, the 6 gate reviews, the final TRUE/FALSE POSITIVE verdict) — work the main agent does. When fp-check runs inline (no subagents dispatched), deleting Stop leaves zero completeness enforcement.

Fix: scope the hooks to where they're actionable

Per the Claude Code hooks docs, hooks declared in skill/agent frontmatter are "scoped to the component's lifecycle and only run when that component is active." That addresses the root cause for both hooks:

  • Overall completeness Stop hook → skills/fp-check/SKILL.md frontmatter. Fires only when the fp-check skill is loaded into the session.

  • Per-subagent output checks → each agent's own frontmatter as a Stop hook, which auto-converts to SubagentStop for subagents:

    • data-flow-analyzer → Phase 1 checks
    • exploitability-verifier → Phase 2 checks
    • poc-builder → Phase 4 checks

    Each fires only when that specific agent runs, so the old single SubagentStop prompt's "identify which agent type this is" branching is no longer needed.

  • Delete plugins/fp-check/hooks/hooks.json.

Hook prompts and the {"ok"/"reason"} response schema are carried over unchanged from the current plugin hooks (the schema was corrected in #129). Behavior is identical when fp-check is in use; the only change is that the hooks no longer fire in unrelated sessions.

Impact

  • Unrelated sessions: no longer pay a 30s LLM turn at every Stop/SubagentStop. Fixes the reported bug for both hooks.
  • Verification rigor: preserved, including inline (no-subagent) runs.
  • Version bumped 1.0.21.0.3.

Validation

$ uv run python3 .github/scripts/check_claude_loadability.py   # passed
$ uv run python3 .github/scripts/check_codex_loadability.py     # passed
$ uv run python3 .github/scripts/validate_plugin_metadata.py    # All plugin metadata is in sync

Plus: YAML frontmatter of all four modified markdown files parses and the hook handlers validate (type: prompt, timeout: 30, ok/reason schema, no stale 'approve'/'block' or {"decision":...}).

CODEOWNERS unchanged (/plugins/fp-check/ @ahpaleus @dguido).

🤖 Generated with Claude Code

)

The plugin-level hooks/hooks.json registered Stop and SubagentStop
hooks with matcher "*". Because plugin hooks are always-on, both fired
on every (sub)agent stop in any session where fp-check was merely
installed — burning a 30s LLM turn and injecting stop-feedback even
when fp-check was never used, then no-opping via the prompt's own
fallback.

Root-cause fix: move the hooks into skill/agent frontmatter, which per
the Claude Code docs is "scoped to the component's lifecycle and only
run when that component is active":

- Overall verification-completeness Stop hook -> fp-check SKILL.md
  frontmatter. Fires only when the fp-check skill is loaded.
- Per-subagent output checks -> each agent's own frontmatter as a Stop
  hook, which auto-converts to SubagentStop. data-flow-analyzer checks
  Phase 1, exploitability-verifier Phase 2, poc-builder Phase 4. Each
  fires only when that specific agent runs, so the single SubagentStop
  prompt's "identify which agent type" branching is no longer needed.
- Delete plugins/fp-check/hooks/hooks.json.

This fixes both hooks (not just Stop) and keeps full coverage including
inline fp-check runs, unlike deleting the Stop hook outright. Hook
prompts and the {"ok"/"reason"} response schema are carried over
unchanged from the current plugin hooks.

Bump 1.0.2 -> 1.0.3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@computerality

Copy link
Copy Markdown
Contributor

This didn't work for me and the claude docs on https://code.claude.com/docs/en/sub-agents say:
For security reasons, plugin subagents do not support the hooks, mcpServers, or permissionMode frontmatter fields. These fields are ignored when loading agents from a plugin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fp-check Stop hook with matcher "*" fires on every unrelated session, causing forced LLM turns

2 participants