#!/bin/sh # irreversible-dispatch-guard.sh — PreToolUse(Agent|Task):擋「把不可逆動作寫成選項」的派工單 # # 🔴 事故(Gitea Leo/arcrun-rag#33,2026-08-09):subagent 未經 leo 同意刪掉兩條遠端分支。 # 根因不是它亂來——是總管的派工單寫了「作廢就刪掉分支」,等於預先授權了一個不可逆動作。 # 它刪掉的那條裡,還有一件它自己標明「等 leo 排序」的工作,一併蒸發。 # # 【對照組,同一天同一個總管】#14 的派工單寫 # 「🔴 刪資料不可逆。動手前先把清單寫在 issue 留言,等總管回覆確認才執行」 # ⇒ 那個 agent 真的停下來等。同一個人一次寫對一次寫錯 ⇒ 靠自律不行,要機械閘。 # # 判準(細節與正則見 irreversible_dispatch_check.py): # 派工單裡出現「不可逆動作」的動詞+對象(刪分支/drop table/rm -rf/force push…) # 且沒被否定(不是「不准刪」這種禁令句) # 且全文找不到「停下來等回覆才執行」這類守門片語 # ⇒ 判定為把不可逆動作寫成收工方可以自己執行的選項,擋下。 # # 🔴 寧可誤擋,不可漏放——這是安全機制,不是文風檢查。豁免走留痕: # 命中那一行尾巴加 `irreversible-ok`。 set -eu INPUT="$(cat)" PROMPT=$(printf '%s' "$INPUT" | python3 -c ' import sys, json try: d = json.load(sys.stdin) print(d.get("tool_input", {}).get("prompt", "") or "") except Exception: print("") ' 2>/dev/null || echo "") [ -z "$PROMPT" ] && exit 0 HOOK_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) RESULT=$(printf '%s' "$PROMPT" | python3 "$HOOK_DIR/irreversible_dispatch_check.py" 2>/dev/null || echo '{"verdict":"OK","hits":[]}') VERDICT=$(printf '%s' "$RESULT" | python3 -c ' import sys, json try: d = json.load(sys.stdin) print(d.get("verdict", "OK")) except Exception: print("OK") ' 2>/dev/null || echo "OK") [ "$VERDICT" != "BLOCK" ] && exit 0 HITS=$(printf '%s' "$RESULT" | python3 -c ' import sys, json try: d = json.load(sys.stdin) for ln, line, word in d.get("hits", []): print(f" 第 {ln} 行(命中「{word}」):{line}") except Exception: pass ' 2>/dev/null || echo "") cat >&2 <