From 4116286ed05fc9a3dbc7b6311bfb445173085be7 Mon Sep 17 00:00:00 2001 From: sjat Date: Wed, 10 Jun 2026 12:51:46 +0200 Subject: [PATCH] feat(hooks): Stop guard blocking the execution-mode menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .claude/hooks/guard-execution-mode-menu.sh | 46 ++++++++++++++++++++++ .claude/settings.json | 12 ++++++ 2 files changed, 58 insertions(+) create mode 100755 .claude/hooks/guard-execution-mode-menu.sh diff --git a/.claude/hooks/guard-execution-mode-menu.sh b/.claude/hooks/guard-execution-mode-menu.sh new file mode 100755 index 0000000..f9f47c7 --- /dev/null +++ b/.claude/hooks/guard-execution-mode-menu.sh @@ -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 diff --git a/.claude/settings.json b/.claude/settings.json index bbf8f01..4e876ca 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -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" + } + ] + } ] } }