cd /news/ai-safety/anti-deferral-hook-important-the-sh-… · home topics ai-safety article
[ARTICLE · art-13902] src=gist.github.com pub= topic=ai-safety verified=true sentiment=· neutral

Anti-deferral hook. IMPORTANT: The .sh file in expects to live in (project dir)/.claude/hooks/ and expects the .txt files to live in the lib subdir at (project dir)/.claude/hooks/lib/.

A developer has created an anti-deferral hook script that scans the last assistant message in a transcript for banned deferral phrases and blocks responses containing them. The script, designed to live in a project's `.claude/hooks/` directory with supporting `.txt` files in a `lib` subfolder, catches chat-response failure modes where an assistant types phrases like "we can revisit this" without filing an operational change. The hook skips lines inside fenced code blocks and honors a dismiss sentinel for meta-discussion of the rule itself.

read5 min publishedMay 16, 2026

| #!/bin/bash | | | | | | | | | | | | | | | | | | | | | | | | set -uo pipefail | | | | INPUT=$(cat) | | TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null || echo "") | | | | if [[ -z "$TRANSCRIPT" ]] || [[ ! -f "$TRANSCRIPT" ]]; then exit 0; fi | | | | REPO_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}" | | | | | | declare -a BANNED_PHRASES=() | | while IFS= read -r LINE; do | | LINE="${LINE%%#}" | | LINE="${LINE#"${LINE%%[![:space:]]}"}" | | LINE="${LINE%"${LINE##[![:space:]]}"}" | | [[ -z "$LINE" ]] && continue | | BANNED_PHRASES+=("$LINE") | | done < "$REPO_ROOT/.claude/hooks/lib/deferral-banned-phrases.txt" | | PRE_EXISTING_DISMISSAL_REGEX="" | | while IFS= read -r LINE; do | | LINE="${LINE%%#}" | | LINE="${LINE#"${LINE%%[![:space:]]}"}" | | LINE="${LINE%"${LINE##[![:space:]]}"}" | | [[ -z "$LINE" ]] && continue | | PRE_EXISTING_DISMISSAL_REGEX="$LINE" | | break | | done < "$REPO_ROOT/.claude/hooks/lib/deferral-pre-existing-regex.txt" | | | | | LAST_ASSISTANT_LINE=$(tac "$TRANSCRIPT" 2>/dev/null | while IFS= read -r LINE; do | | ROLE=$(echo "$LINE" | jq -r '(.message.role // .role) // empty' 2>/dev/null || echo "") | | if [[ "$ROLE" == "assistant" ]]; then | | echo "$LINE" | | break | | fi | | done) | | | | if [[ -z "$LAST_ASSISTANT_LINE" ]]; then exit 0; fi | | | | | TEXT=$(echo "$LAST_ASSISTANT_LINE" | jq -r ' | | (.message.content // .content // []) as $c | | | if ($c | type) == "array" then | | $c | map(select(type == "object" and .type == "text") | .text) | join("\n") | | elif ($c | type) == "string" then | | $c | | else | | "" | | end | | ' 2>/dev/null || echo "") | | | | if [[ -z "$TEXT" ]]; then exit 0; fi | | | | | if echo "$TEXT" | grep -qF '<!-- deferral-meta -->'; then exit 0; fi | | | | | TEXT_NO_FENCES=$(echo "$TEXT" | awk ' | | /[1]*```/ { in_block = !in_block; next } | | !in_block { print } | | ') | | | | | declare -a HITS=() | | | | for PHRASE in "${BANNED_PHRASES[@]}"; do | | while IFS= read -r MATCH; do | | [[ -z "$MATCH" ]] && continue | | HITS+=("L$MATCH :: phrase="$PHRASE"") | | done < <(echo "$TEXT_NO_FENCES" | grep -inF "$PHRASE" 2>/dev/null) | | done | | | | | while IFS= read -r MATCH; do | | [[ -z "$MATCH" ]] && continue | | if echo "$MATCH" | grep -iqE "$PRE_EXISTING_DISMISSAL_REGEX"; then | | HITS+=("L$MATCH :: phrase="pre-existing (dismissal)"") | | fi | | done < <(echo "$TEXT_NO_FENCES" | grep -inF "pre-existing" 2>/dev/null) | | | | if [[ ${#HITS[@]} -eq 0 ]]; then exit 0; fi | | | | { | | echo "[response-deferral-phrase-gate] BLOCK: banned deferral phrase(s) in your last response." | | echo "" | | echo "Hits (line numbers relative to your last message, code blocks excluded):" | | for HIT in "${HITS[@]}"; do | | echo " $HIT" | | done | | echo "" | | echo "Per .claude/rules/no-ephemeral-deferrals.md, every deferral-shaped" | | echo "statement must resolve via case 1, case 2, or case 3:" | | echo "" | | echo " case 1: revise an existing pending change + /opsx:apply this turn" | | echo " case 2: /opsx:propose <name> -> apply -> sync -> archive, this turn" | | echo " case 3: /opsx:new <name> proposal-only + loud ## DEFERRED: callout" | | echo "" | | echo "If you genuinely need to discuss the rule itself, include the sentinel" | | echo "<!-- deferral-meta --> in your response. The bar for using the sentinel" | | echo "is HIGH -- it dismisses the gate entirely for that response." | | echo "" | | echo "Otherwise: rewrite the deferral sentence to close the loop, or delete" | | echo "it (the concern was not real), and respond again." | | } >&2 | | | | exit 2 |


  1. [:space:] ↩︎

── more in #ai-safety 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/anti-deferral-hook-i…] indexed:0 read:5min 2026-05-16 ·