Files
ISEP/system-dev/wiki/mistakes.md
Leo 3bc7f3c5f5 派工單只剩票號——閘從驗「有沒有票號」改成驗「是不是只有票號」
leo 2026-08-27(inkstone/ISEP#30 comment 4322/4325/4327):
「這些話票上都沒有,你根本沒照規則做事,你的 hook 讓你這樣搞?」
「你用一個 output parser 把你給 subagent 的指令規範,分作幾點,每一點規定格式,
  照這種散文寫法根本無法迭代」「警察也不能抓」
「交件方式不需要寫,定義在原則裡⋯⋯每次都一樣提取出來變成共通規定」
「(那些 session 事實)這些為什麼不寫到票裡?」「subagent 回覆時要表明身份」

病根:no-ticket-no-dispatch.sh 驗的是「有沒有一行【工單】owner/repo#N」,
而規則的原文是「派工單只寫票號」。⇒ 把 40 行任務全寫在 prompt 裡、票號補一行,
閘照樣放行。2026-08-27 一天內這樣做了 5 次,每次票上都沒有那份任務。
規則存在,閘只驗了它的殼——同款第 N 次(history-first/KBDB-first/stage-first)。

新增 hooks/dispatch-format-guard.sh(PreToolUse Task|Agent),兩件事:
- 擋:【工單】以外還有實質內容就 exit 2,並指出那些內容該搬去哪
  (每次都一樣 → 共通規定;這次才知道 → 寫進那張票。
   判準「這句話換一張票還成立嗎?」)
- 注入:合規的派工自動把共通規定送給收工方(交件方式、不准 push main、
  org 是 inkstone…)——這是「派工單只剩票號」能成立的前提,
  leo 的驗收條件之一就是「收工方沒讀派工單也知道要貼回原票」

判準是結構不是文字(leo 2026-08-17 那條檢驗):問的是「這一行是不是【工單】欄位」
——在不在,不是寫什麼。hooks/lib/dispatch_parse.py 全檔零個「命中某個詞就違規」的比對。
⇒ 也因此不需要語意判官:免費、瞬間、每次結果一樣。

新增 hooks/reply-identity-guard.sh(PreToolUse Bash)+ scripts/ticket 內建檢查:
票上每一則留言第一行要有【身份】(總管/subagent/leo)。貼留言有兩條路,兩條都封
——ticket-api-bypass-guard 是刻意放行「對既有票留言」的,只封正門等於沒封。
實害:多條線並行時總管寫的診斷被當成 subagent 的結論,而其中一則是錯的。

規約寫成文件:docs/governance/dispatch-and-reply-format.md
(§2 那段就是被注入的那份共通規定本體——只有一份,改那裡等於改所有派工)

實測(離線、不打網路、不花錢):
  hooks/tests/dispatch-format-guard.test.sh   19/19
  hooks/tests/reply-identity.test.sh          11/11
測資裡的 B⑨ 是真跡:產生 ISEP#30 這條線的那一次派工,一字未改。
另 4 份 leo 點名的違規派工拿不回來了——它們住在 prompt 裡,agent 一停就沒了,
這件事本身就是這條規則的證據(見 hooks/tests/fixtures/README.md,不用想像的例子替補)。

升版 0.4.0 → 0.5.0(產物按版本號分資料夾,不升版新閘不會被載入)。
tag 照慣例打在 merge commit 上,所以這條分支上 check-version-consistency.sh 是紅的。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 11:35:03 +08:00

7.7 KiB
Raw Permalink Blame History

已知誤解 / 踩過的坑

這是 ISEP 自己的坑,不是 InkStoneCo 的(不轉抄,複製即 fork,fork 即漂移)。 撞到新坑就 append 一條;session 開場只推最近幾條標題(見 hooks/session-start-recall.sh push 5/5),全文在這裡。

⚠️ MISTAKE: 「環境設定」曾經拆成兩份,各自會漂 症狀: 本機在跑 InkStoneCo/.claude/(真身),雲端跑的是 generate-shell-payload.py 另外產生塞進 GitHub 私 repo 的一份(薄殼)。inkstone/InkStoneCo#57 實測:薄殼比真身 少 7 支閘,其中兩支是前一天才立的;#14 更早查到雲端 33 支 guard 一支都沒生效。 正確做法: 只留一份——ISEP 這個 repo 本身就是唯一真相源,本機與雲端裝同一個 plugin。 改動一律只改這裡,然後兩邊各自 /plugin update。不要再改 InkStoneCo/.claude/hooks/(退場中,早晚會刪)。 原因: 「一份東西兩個副本」在沒有機制強制同步的情況下必然漂移——差異不是誰疏忽, 是結構本身允許漂移發生。 日期: 2026-08-20ISEP c263866 建立時就是為了解這個病)

⚠️ MISTAKE: hook 路徑寫死會在雲端斷 症狀: 舊版 hook 若用 $CLAUDE_PROJECT_DIR/.claude/hooks/... 或寫死的絕對路徑指向 hook 腳本自己,雲端執行時的 cwd 不是本機那個真身目錄,路徑就對不到、hook 直接失效 (正是上一條「雲端 33 支 guard 一支都沒生效」的根因)。 正確做法: hook 指自己(找到自己在哪、要 source 的其他 hook 檔)一律用官方 ${CLAUDE_PLUGIN_ROOT}。腳本內部要指專案裡的檔案(如 system-dev/wiki/system-dev/docs/)才用 $CLAUDE_PROJECT_DIR——那些檔案本來就该住在被操作的 那個 repo 裡,跟 hook 自己的路徑是两回事,别混。 原因: 兩種路徑指的是完全不同的東西(「plugin 安裝到哪」vs「正在操作哪個專案」), 混用就是這條坑的直接原因。 日期: 2026-08-20README「路徑規約」段記錄,ISEP 0.1.0 把 51 條 hook 路徑全部改過一輪)

⚠️ MISTAKE: 「裝好了」不等於「它在跑」

2026-08-20 實查:ISEP repo 建好、README 寫著 0.1.0、41 支閘都在裡面—— 但 claude plugin list根本沒有 ISEP。本機仍然在跑 InkStoneCo/.claude/ Gitea 上一個 release tag 都沒有

⇒ 「東西做出來了」與「有人在用它」是兩件事,而只有後者算交付。 ⇒ 判準:去執行環境查它有沒有被載入claude plugin list / claude plugin details), 不要從 repo 裡有什麼檔案去推論。

⚠️ MISTAKE: 判準寫在閘裡了,但那個閘掛在做完之後才跑的時機上

票: inkstone/InkStoneCo#55 日期: 2026-08-26

症狀: leo 一天內好幾次被丟純技術路徑選擇,當場問「今天已經好幾次問我, 為什麼 hooks 沒有攔下來?」,其中一次他直接說「這種問題不要問我, 我要的是你解決了以後給我 prod」。

實查: 總管問 leo 走的動作是 AskUserQuestion 這個工具,而 hooks.jsonAskUserQuestion 出現 0 次——沒有任何 matcher,它是裸的。 判準其實早就寫好了(self-drive-police.sh / self-drive-judge.sh 用的就是四題公式), 但那兩支只掛在 StopSubagentStop

原因: 判準對了,時機錯了。 Stop 是回合結束後才跑——問題早就送到 leo 眼前、 他早就被打斷了,這時再反問 AI「你查過了嗎」,成本已經轉嫁出去了。

正確做法: 攔截點要長在那個動作發生的那一刻PreToolUse / AskUserQuestion)。 新增 hooks/ask-user-question-guard.sh🔴 推廣:以後看到「規則寫了卻沒被攔下來」,先問的不是「判準對不對」, 而是「這支閘掛在哪個事件上、那個事件發生時傷害造成了沒有」。

⚠️ MISTAKE: hook 訊息用沒加引號的 heredoc,反引號會被當成命令執行

票: inkstone/InkStoneCo#55 日期: 2026-08-26

症狀: ask-user-question-guard.sh 擋下之後,stderr 冒出 line 218: system-dev/wiki/: is a directory,而訊息裡 「去查 system-dev/wiki/」和「touch /tmp/.ask-ok-<session_id>」兩行 變成空白。閘照擋 exit 2,所以測試若只看離開碼完全看不出來

原因: 寫成 cat >&2 <<EOF(heredoc 標記沒加引號)⇒ shell 會對內容做展開, 而本 repo 的 hook 訊息慣例上大量使用反引號標路徑與指令 ⇒ 每一組反引號都被當成命令替換真的去執行。

正確做法: hook 的訊息一律用 cat <<'EOF'(標記加單引號)。 需要塞變數就留 __PLACEHOLDER__,事後用 python 換掉—— 不要用 sed,正體中文加上訊息裡的 /&\ 讓跳脫非常脆。 迴歸測試要檢查訊息內容,不能只檢查離開碼 hooks/tests/ask-user-question-guard.test.sh 的 ⑩b 就是這一條)。

⚠️ MISTAKE: 閘只驗了規則的,沒驗規則本身

no-ticket-no-dispatch.sh 掛在派工的當下,檢查「派工單裡有沒有一行 【工單】owner/repo#N」。 規則的原文卻是「不准把票上已經有的東西再抄一遍進派工單⋯⋯派工單只寫票號」。

⇒ 於是可以把 40 行任務全寫在 prompt 裡、票號補一行,閘照樣放行。 ⇒ 2026-08-27 一天之內這樣做了 5 次,每一次票上都沒有那份任務。 leo:「這些話票上都沒有,你根本沒照規則做事,你的 hook 讓你這樣搞?

根因不是那支閘寫壞了,是它驗的東西比規則小。 「有沒有票號」是規則最容易機械化的那一格,所以它被實作了; 「任務有沒有真的落在票上」比較難,所以沒有——而漏掉的那格才是規則的本體。

判準:寫完一支閘,回頭把規則原文逐句對一次,問「這一句被驗到了嗎」。 只驗得到最容易的那一格 ⇒ 那支閘會製造「有在管」的錯覺,比沒有閘更危險。 ⇒ 同款:history-firstKBDB-firststage-firstAskUserQuestion 裸奔,全是這個形狀。

修法(v0.5.0):dispatch-format-guard.sh——派工單 票號,多一個字都擋。 規則變得比原本更嚴,反而更好驗:判準從「內容夠不夠」變成「這一行是不是【工單】欄位」, 純結構、不用語意判官、每次結果一樣。 規約:docs/governance/dispatch-and-reply-format.md

日期: 2026-08-27inkstone/ISEP#30 comment 432243254327

⚠️ MISTAKE: 「這是 session 才知道的事」被當成寫進 prompt 的正當理由

派工鐵律允許派工單帶「這個 session 才知道、票上還沒有的事」。 總管照字面理解,把 TCC 權限、main 是哪顆 commit、正本能從哪裡 clone 三件事寫進 prompt。

leo 當場:「這些為什麼不寫到票裡?

「票上還沒有」不是把它寫進 prompt 的理由——它就是「去把它寫上票」的指令。 ⇒ 實害(同日):總管停掉重派 3 次,前兩次的任務與 session 事實全部隨 prompt 蒸發。 票活得比任何一個 agent 久,prompt 不是。

判準:「這句話換一張票還成立嗎?」 還成立 ⇒ 共通規定(docs/governance/dispatch-and-reply-format.md §2,機器自動注入)。 只有這次成立 ⇒ 寫進那張票。兩種都不進派工單。

日期: 2026-08-27inkstone/ISEP#30 comment 4327