#!/usr/bin/env bash # duplicate-hook-registration-guard.sh — SessionStart:這個專案自己的 .claude/settings.json # 是不是還在登記一支跟 ISEP plugin 同名的舊 hook(inkstone/ISEP#122) # # 為什麼存在(2026-09-01 實撞): # 總管三次要部署 uncle6.me,三次被 prod-write-guard.sh 擋。leo 問「你沒有權限?」 # ——不是沒權限,是 InkStoneCo/.claude/settings.json 裡還留著 ISEP 立 plugin 之前 # 的舊登記,跟 plugin 自己的 hooks.json 同時掛在同一個事件上,於是每個 PreToolUse # 都跑了**兩次**:一次由專案版、一次由 plugin。多數閘只是白跑一次沒感覺, # 但**帶「蓋一次戳記只放行一次」語意的閘會被第一份吃光**——第二份永遠看到空的戳記, # 於是恆擋。查證見 ISEP#122:37 支專案版登記裡,34 支跟 plugin 完全同名同用途、 # 3 支是已經退役的機制(`claim-verify-police.sh`/`subagent-claim-worksheet.sh`/ # `sdd-guard.sh`),全部清掉;剩下 1 支(`kv-write-guard.sh`)是專案版有、plugin # 當時沒有的真閘,已原樣搬進本 repo(同一輪 commit)。 # # 🔑 這支 hook 的用途不是清除,是**讓「又長出重複掛載」這件事有人立刻知道**。 # 上一次它能存活 13 天沒被發現,就是因為除了「戳記莫名其妙失效」以外沒有任何 # 訊號會指出來——一支天天在跑的閘,跟一支不存在的閘,行為長得一模一樣。 # # 三條設計約束(跟 skill-deploy-drift-guard.sh 同一個形狀): # 1. 沒事不出聲——乾淨時完全靜音。 # 2. 從不擋人——SessionStart 一律 exit 0,這是通知,不是閘。 # 3. 判準是「登記在不在」,不是關鍵字——比對的是 hooks.json/settings.json # 裡實際出現的檔名,不是靠猜或列黑名單(isep-hand 紅線:不准用關鍵字黑名單)。 set -uo pipefail ROOT="${CLAUDE_PLUGIN_ROOT:-}" [ -n "$ROOT" ] || exit 0 [ -f "$ROOT/hooks/hooks.json" ] || exit 0 PROJ="${CLAUDE_PROJECT_DIR:-}" [ -n "$PROJ" ] || exit 0 SETTINGS="$PROJ/.claude/settings.json" [ -f "$SETTINGS" ] || exit 0 # ISEP plugin 自己「真的註冊了」的 hook 檔名(不是目錄列表——目錄裡可能有沒掛的樣板) PLUGIN_NAMES="$(grep -oE 'hooks/[a-zA-Z0-9._-]+\.sh' "$ROOT/hooks/hooks.json" 2>/dev/null \ | sed 's|.*/||' | sort -u)" [ -n "$PLUGIN_NAMES" ] || exit 0 # 這個專案自己的 settings.json 裡,指向 .claude/hooks/ 底下的登記 PROJECT_NAMES="$(grep -oE '\.claude/hooks/[a-zA-Z0-9_.-]+\.sh' "$SETTINGS" 2>/dev/null \ | sed 's|.*/||' | sort -u)" [ -n "$PROJECT_NAMES" ] || exit 0 DUPES="$(comm -12 <(printf '%s\n' "$PLUGIN_NAMES") <(printf '%s\n' "$PROJECT_NAMES") 2>/dev/null || true)" [ -n "$DUPES" ] || exit 0 N=$(printf '%s\n' "$DUPES" | grep -c .) LIST="$(printf '%s\n' "$DUPES" | sed 's/^/ • /')" cat <