Development record
After the hook commands became one-line wrappers, a guard refusal still reaches the person as 'PreToolUse:<tool> hook error: [sh "${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-use.sh"]: <message>', framed as an error, because the guard blocks by exiting 2 with its message on stderr. Claude Code's hooks reference documents a structured alternative for PreToolUse: exit 0 with hookSpecificOutput.permissionDecision "deny" and permissionDecisionReason on stdout, which blocks the call (a question included) and hands the model only the reason; the command echo in front of an exit-2 message is not documented at all. Unverified here: the guide that reported it worked from summarised documentation.
After the hook commands became one-line wrappers, a guard refusal still reaches the person as 'PreToolUse:<tool> hook error: [sh "${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-use.sh"]: <message>', framed as an error, because the guard blocks by exiting 2 with its message on stderr. Claude Code's hooks reference documents a structured alternative for PreToolUse: exit 0 with hookSpecificOutput.permissionDecision "deny" and permissionDecisionReason on stdout, which blocks the call (a question included) and hands the model only the reason; the command echo in front of an exit-2 message is not documented at all. Unverified here: the guide that reported it worked from summarised documentation.
Measured 2026-10-07 on Claude Code 2.1.292, in live interactive sessions in a scratch repository whose only hook was a project-level PreToolUse hook, for the shell tool and the question tool alike:
- Exit 2 with the reason on stderr shows the person
Error: PreToolUse:<tool> hook error: [<hook command>]: <reason>. - Exit 0 with
permissionDecision"deny" showsError: PreToolUse:<tool> hook error: <reason>: the command echo goes, the error framing stays. The legacydecision"block", a deny withsuppressOutput, and a deny withsystemMessagerender the same framing (the last adds a second line). - Exit 1 shows the person a
hook errorline and the model never receives the message. - Exit 0 with
additionalContextand no decision lets the call run with no error line, and the model receives the note after the tool returns.
So the host frames every refusal as an error, and the framing itself is outside abcd's reach. The guard now refuses through the JSON deny (the shorter line), and a question whose only fault is its length is shown with a note to the agent rather than refused, which removes the most frequent refusal the person saw. A refusal of a shell hazard, of another layout rule, or of a question asked while the mode reads managed still shows the shorter error line. The before and after captures are kept in the local tier of the checkout that measured them.
Frontmatter
| id | iss-2610070637562567 |
|---|---|
| type | issue |
| lifecycle | open |
| severity | minor |
.abcd/work/issues/open/iss-2610070637562567-after-the-hook-commands-became-one-line.md
open on GitHub ↗ · commit history ↗
mentions9
- iss-2610072113549994A user-visible change reached main with no record, so the derived changelog would not announce it: the guard hook refuses through the host's JSON deny, so a refused command no longer echoes the hook's command line before the guard's message; a question whose only fault is its length is shown with a note to the agent rather than refused; and a new abcd:question-drafter agent drafts abcd's questions so they fit. Merged as 12a5dca2f; iss-2610070637562567 stays open because the host still draws every hook refusal as an error.
- iss-2610090744325698The product thinker's 2026-10-07 lesson, that a user-visible host fix is proven by a capture of a live session before and after the change and never by script output alone (v0.13.2's hook fix passed its tests while every live session still showed the error), is held only in one agent's local memory. No committed rule, principle or definition of done carries it; its only committed trace is the measurement inside iss-2610070637562567, so another session or contributor never receives it.
- iss-2610090816296964When abcd asks the person to choose, the question keeps running past the host's question box: a question over 24 rows at 80 columns was refused in a live session on the 0.13.2 plugin, and the agent cut its options down to a few words each to get it through. The product thinker, reading the refused draft, saw that its long text added nothing: a paragraph before the options described each option again, while the options' own labels and descriptions were already perfectly clear. The problem was repetition, not too little room, so the fix is a rule against it rather than a second channel for the overflow; a separate text file for the full options was considered and dropped on the product thinker's ruling of 2026-10-09.
- iss-2610100626211810An abcd question is refused with 'the mode reads managed' whenever the agent has not run abcd mode just before it, although the question's own chip ('Tech Q3', 'Product Q2') already names whom it is for. The mode is reset two ways: the asking rules tell the agent to set it back to managed after every answer, and the prompt hook resets it on the next prompt the session receives while a question is marked open, which in a turn of several questions can be a peer session's message or a message the person sends mid-turn (inferred from a refusal on 2026-10-10 after a peer message arrived; not yet reproduced). So an interview of several questions is refused intermittently, and the person sees the hook's error line each time (iss-2610070637562567 measured that every refusal reaches them framed as an error). The product thinker chose on 2026-10-10 to fix this in code first, before the general rewriter.
- iss-2610100626327722When an abcd question is refused for another fault, the refusal lists the question's rows overrun among the parts to fix ('fix each and ask again'), although the rows limit alone never refuses (the question gate's rowsOnly, iss-2610070637562567). The person and the agent read it as a cause of the refusal, and the agent cuts a question the gate would have shown; seen twice in one interview on 2026-10-10.
- iss-2610150558540639The person still sees the question guard's refusals as errors while a session rewrites a question. On 2026-10-15 one question to the product thinker was refused three times before it reached them: the header, three sentences per option, the row limit, a missing Change later line and a named command. Each refusal showed on their screen as a hook error, because the session drafted the question itself instead of through the drafting agent
- itd-201Every question abcd's agents put to a human is asked one at a time, in plain language, with options that widen
- itd-2610230756259215Every question abcd asks passes its check before the person sees it
- spc-2610301917245442Every question abcd asks passes its check before the person sees it