From ec63a2d25b28232176309aa020f674b1a8238df8 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 11 Jun 2026 20:17:25 -0700 Subject: [PATCH] feat: deploy the Conductor AskUserQuestion hook (setup + upgrade migration) The PreToolUse hook only delivers its Conductor-prose guarantee if it's installed, but setup skips hook registration in non-interactive (conductor/CI) setups. Two fixes so layer 3 actually deploys: - setup: treat a Conductor workspace as an implicit opt-in for the PreToolUse hook on the silent fall-through (never overriding an explicit opt-out). - migration v1.58.0.0: re-register the hook for existing Conductor installs on /gstack-upgrade, idempotent and respecting plan_tune_hooks=no. Co-Authored-By: Claude Fable 5 --- gstack-upgrade/migrations/v1.58.0.0.sh | 63 ++++++++++++++++++++++++++ setup | 20 +++++++- 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100755 gstack-upgrade/migrations/v1.58.0.0.sh diff --git a/gstack-upgrade/migrations/v1.58.0.0.sh b/gstack-upgrade/migrations/v1.58.0.0.sh new file mode 100755 index 000000000..da2252286 --- /dev/null +++ b/gstack-upgrade/migrations/v1.58.0.0.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Migration: v1.58.0.0 — register the PreToolUse AskUserQuestion hook for +# existing Conductor installs. +# +# Why a migration: v1.58 makes the PreToolUse question-preference-hook also +# deny the flaky Conductor AskUserQuestion and redirect to a prose decision +# brief. But setup's hook-install block skips silently in non-interactive +# (conductor/CI) setups, and existing users who previously declined plan-tune +# hooks would never pick up the new Conductor backstop. This re-registers the +# hook for Conductor users so layer 3 actually deploys. +# +# Affected: users who run gstack inside Conductor and don't already have the +# PreToolUse hook installed. +# +# Scope guard: only acts inside a Conductor session (CONDUCTOR_* present) and +# never overrides an explicit `plan_tune_hooks` opt-out. +# +# Idempotent: gstack-settings-hook dedupes by (event, matcher, source), and a +# .done touchfile gates re-runs. + +set -u + +GSTACK_HOME="${HOME}/.gstack" +MIGRATION_DIR="${GSTACK_HOME}/.migrations" +DONE="${MIGRATION_DIR}/v1.58.0.0.done" +mkdir -p "${MIGRATION_DIR}" 2>/dev/null || true +[ -f "${DONE}" ] && exit 0 + +# Only relevant inside Conductor — the prose-default behavior is Conductor-scoped. +if [ -z "${CONDUCTOR_WORKSPACE_PATH:-}" ] && [ -z "${CONDUCTOR_PORT:-}" ]; then + touch "${DONE}" + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +SETTINGS_HOOK="${SCRIPT_DIR}/bin/gstack-settings-hook" +PREF_HOOK="${SCRIPT_DIR}/hosts/claude/hooks/question-preference-hook" +CONFIG_BIN="${SCRIPT_DIR}/bin/gstack-config" + +# Respect an explicit opt-out — don't force a hook on a user who said no. +_PT=$("${CONFIG_BIN}" get plan_tune_hooks 2>/dev/null || echo "") +_PT=$(printf '%s' "${_PT}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]') +case "${_PT}" in + n|no|false|skip|off|0) + echo " [v1.58.0.0] plan_tune_hooks opted out — leaving Conductor on guidance-only prose." >&2 + touch "${DONE}" + exit 0 + ;; +esac + +if [ -x "${SETTINGS_HOOK}" ] && [ -x "${PREF_HOOK}" ]; then + "${SETTINGS_HOOK}" add-event \ + --event PreToolUse \ + --matcher '(AskUserQuestion|mcp__.*__AskUserQuestion)' \ + --command "${PREF_HOOK}" \ + --source plan-tune-cathedral \ + --timeout 5 2>/dev/null \ + && echo " [v1.58.0.0] Conductor AskUserQuestion prose hook registered (PreToolUse)." >&2 \ + || echo " [v1.58.0.0] WARN: could not register the PreToolUse hook; run ./setup --plan-tune-hooks." >&2 +fi + +touch "${DONE}" +exit 0 diff --git a/setup b/setup index ec1db22b7..e4d31f881 100755 --- a/setup +++ b/setup @@ -1371,6 +1371,17 @@ if [ "$NO_TEAM_MODE" -ne 1 ] \ *) PT_DECISION="prompt" ;; esac + # Conductor host reliability: the PreToolUse preference hook also carries the + # Conductor-prose enforcement (deny the flaky mcp__conductor__AskUserQuestion, + # redirect to a prose decision brief). A Conductor workspace setup otherwise + # falls through to "prompt" → the non-interactive skip below, leaving Conductor + # users without that backstop. Treat Conductor as an implicit opt-in — but + # only on the silent fall-through, never overriding an explicit --no-plan-tune-hooks. + if [ "$PT_DECISION" = "prompt" ] && { [ -n "${CONDUCTOR_WORKSPACE_PATH:-}" ] || [ -n "${CONDUCTOR_PORT:-}" ]; }; then + PT_DECISION="yes" + _PT_CONDUCTOR_AUTO=1 + fi + _install_plan_tune_hooks() { "$SETTINGS_HOOK" add-event \ --event PostToolUse \ @@ -1405,10 +1416,15 @@ if [ "$NO_TEAM_MODE" -ne 1 ] \ log "" log "Plan-tune hooks already installed. Run \`$SETTINGS_HOOK list-sources\` to inspect." elif [ "$PT_DECISION" = "yes" ]; then - # Explicit opt-in (flag / env / config). Non-interactive. + # Explicit opt-in (flag / env / config) or Conductor implicit opt-in. Non-interactive. _install_plan_tune_hooks log "" - log "Plan-tune hooks installed. Run /plan-tune anytime to inspect." + if [ "${_PT_CONDUCTOR_AUTO:-0}" -eq 1 ]; then + log "AskUserQuestion reliability hooks installed (Conductor detected): decisions" + log "render as a prose brief instead of the flaky AskUserQuestion tool. Inspect with /plan-tune." + else + log "Plan-tune hooks installed. Run /plan-tune anytime to inspect." + fi touch "$PLAN_TUNE_INSTALL_MARKER" elif [ "$PT_DECISION" = "no" ]; then # Explicit opt-out (flag / env / config). Non-interactive.