feat(hooks): Stop guard blocking the execution-mode menu
Mechanical fix for the 4×-recurring execution-mode menu ask (kaizen 2026-06-10). A Stop hook reads the transcript and, if the final assistant message presents the "subagent-driven vs inline — which approach?" menu, blocks the turn and tells the model to proceed subagent-driven (boma's standing preference). Fails open, respects stop_hook_active (no loop), tight match signature (no false positives on meta-discussion). Pipe-tested across 5 scenarios. Activates next session (settings watcher only tracks files present at session start). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
91713127cb
commit
4116286ed0
2 changed files with 58 additions and 0 deletions
46
.claude/hooks/guard-execution-mode-menu.sh
Executable file
46
.claude/hooks/guard-execution-mode-menu.sh
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Stop guard: block ending the turn when the assistant's final message presents the
|
||||
# execution-mode menu. The writing-plans / subagent-driven-development skills script a
|
||||
# "Subagent-Driven vs Inline Execution — which approach?" menu at the plan→execution
|
||||
# handoff. boma's standing preference (docs/FRICTION.md + the
|
||||
# always-subagent-driven-execution memory) is to NEVER present it and proceed
|
||||
# subagent-driven. Prose reminders failed four times (06-05/06/09/10); this is the
|
||||
# mechanical guard recorded by the 2026-06-10 kaizen review.
|
||||
#
|
||||
# Fails OPEN: any parse/read problem → allow the stop. Respects stop_hook_active so a
|
||||
# block can never loop. The match signature is deliberately tight ("inline execution"
|
||||
# AND "which approach"/"two execution options") so it fires on the actual menu, not on
|
||||
# meta-discussion of it.
|
||||
#
|
||||
set -uo pipefail
|
||||
|
||||
input=$(cat 2>/dev/null) || exit 0
|
||||
|
||||
# Loop guard: if we already blocked once for this stop, let it through.
|
||||
active=$(printf '%s' "$input" | jq -r '.stop_hook_active // false' 2>/dev/null) || exit 0
|
||||
[ "$active" = "true" ] && exit 0
|
||||
|
||||
transcript=$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null) || exit 0
|
||||
[ -z "$transcript" ] || [ ! -r "$transcript" ] && exit 0
|
||||
|
||||
# Last assistant message's text blocks, joined.
|
||||
text=$(jq -rs '
|
||||
([ .[] | select(.type=="assistant") ] | last) as $a
|
||||
| ($a.message.content // [])
|
||||
| if type=="array" then [ .[] | select(.type=="text") | .text ] | join("\n")
|
||||
elif type=="string" then .
|
||||
else "" end
|
||||
' "$transcript" 2>/dev/null) || exit 0
|
||||
|
||||
low="${text,,}"
|
||||
|
||||
if [[ "$low" == *"inline execution"* \
|
||||
&& ( "$low" == *"which approach"* || "$low" == *"two execution options"* ) ]]; then
|
||||
cat <<'JSON'
|
||||
{"decision":"block","reason":"Execution-mode menu detected in your final message. boma standing preference (docs/FRICTION.md + always-subagent-driven-execution memory): never present the subagent-driven-vs-inline menu. Drop the menu and proceed with subagent-driven execution directly (superpowers:subagent-driven-development)."}
|
||||
JSON
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
|
@ -56,6 +56,18 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/guard-execution-mode-menu.sh\"",
|
||||
"timeout": 10,
|
||||
"statusMessage": "Checking for execution-mode menu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue