0
Fork 0
mirror of https://github.com/trailofbits/skills.git synced 2026-09-27 09:29:06 +00:00

fp-check: Stop/SubagentStop prompt hooks block every non-fp-check session ("approve" has no mapping to the ok schema) #342

Open
opened 2026-09-26 23:14:58 +00:00 by GeeksikhSecurity · 0 comments
GeeksikhSecurity commented 2026-09-26 23:14:58 +00:00 (Migrated from github.com)

Title: fp-check: Stop/SubagentStop prompt hooks block every non-fp-check session ("approve" has no mapping to the ok schema)

Plugin: fp-check@trailofbits 1.0.0, plugins/fp-check/hooks/hooks.json

What happens

With fp-check installed at user scope, the Stop hook fires in every Claude Code session ("matcher": "*"). In sessions that have nothing to do with fp-check, it blocks the agent from stopping. The hook's own reasoning says the session should be approved:

This conversation is not about fp-check false positive analysis at all ... the appropriate response should be approve, not block.

It still returns a blocking result. I saw this 6+ times in a single session on a non-security task. Each block forces the agent into another turn.

Cause

The prompts tell the evaluating model to "return 'approve'" or "return 'block'". A type: "prompt" hook's response, though, must be {"ok": boolean, "reason": string}. Nothing tells the model how "approve" maps onto that shape, so it often returns ok: false with "approve" written in reason, which blocks. SubagentStop has the same wording ("If the agent is not an fp-check agent, return 'approve'").

Suggested fix

Replace the last three lines of each prompt with explicit ok values, and put the not-fp-check case first. The checklists above them stay unchanged.

--- Stop
-If ANY bug is missing ANY of these phases or the gate review, return 'block' with the specific gaps.
-If all bugs have complete verification with verdicts, return 'approve'.
-If the conversation is not about fp-check verification at all, return 'approve'.
+Respond ONLY with JSON matching {"ok": boolean, "reason": string}. "ok": true lets the agent stop; "ok": false blocks it. Decide in this order:
+1. If the conversation is not about fp-check verification at all (the fp-check skill was not invoked and no suspected bug is being taken through these phases), respond {"ok": true, "reason": "not an fp-check session"}. This is the usual case.
+2. If every bug being verified has complete verification with a verdict, respond {"ok": true, "reason": "verification complete"}.
+3. Otherwise (ANY bug is missing ANY of these phases or the gate review), respond {"ok": false, "reason": "<the specific gaps>"}.

--- SubagentStop
-If the agent is not an fp-check agent, return 'approve'.
-If required sections are missing, return 'block' with the specific gaps.
-If all required sections are present with evidence, return 'approve'.
+Respond ONLY with JSON matching {"ok": boolean, "reason": string}. "ok": true lets the subagent stop; "ok": false blocks it. Decide in this order:
+1. If the agent is not one of the three fp-check agents above, respond {"ok": true, "reason": "not an fp-check agent"}.
+2. If all required sections are present with evidence, respond {"ok": true, "reason": "output complete"}.
+3. Otherwise, respond {"ok": false, "reason": "<the specific missing sections>"}.

Worth considering

Because the matcher is *, every stop in every session pays for a model call (30 s timeout). The hook could run only while fp-check is actually in use, for example with a marker file the skill writes and a command hook that exits 0 immediately when the marker is absent. That would remove the cost as well as the false blocks.

**Title:** fp-check: Stop/SubagentStop prompt hooks block every non-fp-check session ("approve" has no mapping to the `ok` schema) **Plugin:** `fp-check@trailofbits` 1.0.0, `plugins/fp-check/hooks/hooks.json` ### What happens With fp-check installed at user scope, the `Stop` hook fires in every Claude Code session (`"matcher": "*"`). In sessions that have nothing to do with fp-check, it blocks the agent from stopping. The hook's own reasoning says the session should be approved: > This conversation is not about fp-check false positive analysis at all ... the appropriate response should be approve, not block. It still returns a blocking result. I saw this 6+ times in a single session on a non-security task. Each block forces the agent into another turn. ### Cause The prompts tell the evaluating model to "return 'approve'" or "return 'block'". A `type: "prompt"` hook's response, though, must be `{"ok": boolean, "reason": string}`. Nothing tells the model how "approve" maps onto that shape, so it often returns `ok: false` with "approve" written in `reason`, which blocks. `SubagentStop` has the same wording ("If the agent is not an fp-check agent, return 'approve'"). ### Suggested fix Replace the last three lines of each prompt with explicit `ok` values, and put the not-fp-check case first. The checklists above them stay unchanged. ```diff --- Stop -If ANY bug is missing ANY of these phases or the gate review, return 'block' with the specific gaps. -If all bugs have complete verification with verdicts, return 'approve'. -If the conversation is not about fp-check verification at all, return 'approve'. +Respond ONLY with JSON matching {"ok": boolean, "reason": string}. "ok": true lets the agent stop; "ok": false blocks it. Decide in this order: +1. If the conversation is not about fp-check verification at all (the fp-check skill was not invoked and no suspected bug is being taken through these phases), respond {"ok": true, "reason": "not an fp-check session"}. This is the usual case. +2. If every bug being verified has complete verification with a verdict, respond {"ok": true, "reason": "verification complete"}. +3. Otherwise (ANY bug is missing ANY of these phases or the gate review), respond {"ok": false, "reason": "<the specific gaps>"}. --- SubagentStop -If the agent is not an fp-check agent, return 'approve'. -If required sections are missing, return 'block' with the specific gaps. -If all required sections are present with evidence, return 'approve'. +Respond ONLY with JSON matching {"ok": boolean, "reason": string}. "ok": true lets the subagent stop; "ok": false blocks it. Decide in this order: +1. If the agent is not one of the three fp-check agents above, respond {"ok": true, "reason": "not an fp-check agent"}. +2. If all required sections are present with evidence, respond {"ok": true, "reason": "output complete"}. +3. Otherwise, respond {"ok": false, "reason": "<the specific missing sections>"}. ``` ### Worth considering Because the matcher is `*`, every stop in every session pays for a model call (30 s timeout). The hook could run only while fp-check is actually in use, for example with a marker file the skill writes and a `command` hook that exits 0 immediately when the marker is absent. That would remove the cost as well as the false blocks.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
skills/trailofbits-skills#342
No description provided.