fix(hooks): 重啟閘門狀態檔改為一支 CLI 一份,修並行覆寫與全域解除 #37
1 Participants
Notifications
Due Date
No due date set.
Blocks
#30 fix(cli): 部署收尾的重啟狀態檔路徑改為一支 CLI 一份
plugins/cli
#28 docs(meta): 準則的重啟閘門狀態檔規則改為一支 CLI 一份
plugins/meta
Reference: plugins/hooks#37
Reference in New Issue
Block a user
摘要
$JSC_HOME/restart-required,部署現場實測抓到兩個後果:require用>覆寫,並行部署互相覆蓋(kiro 寫入 9 秒後被 codex 蓋掉,domains=與cli=不再代表 kiro);clear用rm -f刪整個檔案,任一支 CLI 重啟就解除全部五支的閘門,其餘四支沒重啟卻不再被擋。這台機器裝了五支 CLI,閘門在多 CLI 環境等於半失效。改為一支 CLI 一份的狀態檔$JSC_HOME/restart-required.d/{CLI 代號},require、判定、clear三件事都只碰自己那一份。變更內容
hooks/restart-gate.shcli_code()(含不能當檔名的髒值防護)與state_line();require只寫自己那一份、hook 判定只讀自己那一份、clear只刪自己那一份、report一支 CLI 一行;舊格式單一檔案存在時一律擋並標明是舊格式紀錄,clear一併刪掉它;檔頭註解同步hooks/session-timer.shtools/wire-cli.shsmoke的重啟閘門段從 9 條情境擴為 16 條,新增smoke_rs_file()比對狀態檔在不在——一支一份的範圍反映在檔案在不在,不反映在結束碼;檔頭的判定路徑清單同步README.mdrestart-gate.sh與session-timer.sh兩列、覆蓋範圍引言、「部署後重啟狀態檔」整節改寫,加上report輸出格式與舊檔相容說明skills/hooks-install/SKILL.mdrestart-required.d/{cli},補上只讀自己那一份、清除只清自己那一份、report一支 CLI 一行、舊檔過渡說明plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json設計重點
$JSC_HOME/restart-required.d/,檔名就是 CLI 代號,四行 key=value 的格式不變。閘門管的是「這一支 CLI 的行程還在跑舊版」,那是每支 CLI 各自的事實,狀態檔不該共用。require寫自己那份、hook 判定只看自己那份、clear只刪自己那份;別支那幾份一律不看。report因此列得出「還有哪幾支沒重啟」,一支一行。version-guard.sh);require回 exit 2 並講明沒有掛上閘門——寫到讀不到的檔名等於沒掛,不能讓部署以為掛上了。/、以.開頭、或出現[A-Za-z0-9._-]以外字元的值一律當成取不到,狀態檔就寫不到狀態目錄外面去。clear除了刪自己那份也刪舊檔——clear只在新工作階段被呼叫,呼叫到就代表確實有一支重啟過了,舊檔留著會讓五支一路被擋到有人手動刪。可移除的時機(所有機器都跑過一次寫狀態目錄的部署與重啟之後)與要移除哪幾段寫在restart-gate.sh檔頭與 README。session-timer.sh,狀態檔的路徑、範圍與格式留在restart-gate.sh,兩邊都不抄對方那一半。JSC_RESTART_GATE=off都照舊,這一輪沒有動到豁免範圍。測試結果
ste100-lint.sh:本存取庫全綠。三份 manifest 版本一致為 0.2.6。sh -n對這次改動過的hooks/restart-gate.sh、hooks/session-timer.sh、tools/wire-cli.sh全數通過。require,產生兩份獨立狀態檔、不互相覆蓋;三支 CLI 各只看自己那一份(kiro 擋、codex 擋、claude 放行);kiroclear之後只剩 codex 那一份;kiro 重啟後放行而 codex 仍被擋——這一列正是舊版壞掉的行為。clear後舊檔被刪,再判定放行。JSC_RESTART_GATE=off放行、取不到技能名放行、取不到 CLI 代號放行。JSC_CLI帶../evil、a/b、.、..四種值都回 exit 2,狀態目錄外沒有多出檔案。</dev/null加timeout下都跑完,沒有等標準輸入的情況。tools/wire-cli.sh smoke claude回status=ok;重啟閘門的 16 條專屬情境,加上每支 hook 都跑一次的那一次通用執行,共 17 行全部與預期相同。前置 Push Request
What:`hooks/restart-gate.sh` 的狀態檔從全機器單一檔案 `$JSC_HOME/restart-required` 改為狀態目錄 `$JSC_HOME/restart-required.d/{CLI 代號}`,一支 CLI 一份,檔名就是 CLI 代號,四行 key=value 的格式不變。新增 `cli_code()` 取當前 CLI 代號(含不能當檔名的髒值防護)與 `state_line()` 統一 `report` 的輸出格式;`require` 只寫自己那一份、hook 判定只讀自己那一份、`clear` 只刪自己那一份、`report` 一支 CLI 一行印出還沒重啟的是哪幾支。舊格式的單一檔案存在時一律擋,訊息標明是舊格式紀錄,`clear` 會一併刪掉它。檔頭註解同步改寫,新增「report 輸出格式」與「舊檔相容(過渡用)」兩節。`hooks/session-timer.sh` 程式碼不動,兩處註解補上「清除範圍只有跑到這支腳本的那一支 CLI」。 Why:這道閘門 2026-08-27 才上線,部署現場實測抓到兩個缺陷。第一,`require` 用 `>` 覆寫單一檔案,並行部署互相覆蓋——kiro 寫入 9 秒後被 codex 蓋掉,`domains=` 與 `cli=` 不再代表 kiro。第二,`clear` 用 `rm -f` 刪整個檔案,任一支 CLI 重啟就解除全部五支的閘門,其餘四支沒重啟卻不再被擋。這台機器就裝了五支 CLI,閘門在多 CLI 環境等於半失效。閘門管的是「這一支 CLI 的行程還在跑舊版」,那是每支 CLI 各自的事實,狀態檔本來就不該共用。 How:寫入、判定、清除三件事都只碰自己那一份,別支那幾份一律不看,`clear` 的範圍也就收斂成呼叫端那一支。取不到 CLI 代號時比照既有的「查不到就放行」原則:hook 模式放行,`require` 回 exit 2 並講明這次沒有掛上閘門——寫到讀不到的檔名等於沒掛,不能讓部署以為掛上了。代號會直接拿去當檔名,所以含 `/`、以 `.` 開頭、或出現 `[A-Za-z0-9._-]` 以外字元的值一律當成取不到,狀態檔就寫不到目錄外面去。舊檔沒有 per-CLI 資訊,分不出是哪一支寫的,判定寧可擋多不擋少;`clear` 只在新工作階段被呼叫,呼叫到就代表確實有一支重新啟動過了,舊檔留著會讓五支一路被擋到有人手動刪,所以一併刪掉。這段相容邏輯與可移除的時機(所有機器都跑過一次寫狀態目錄的部署與重啟之後)寫在檔頭「舊檔相容」與 README。`session-timer.sh` 不必跟著改:狀態檔的路徑、範圍與格式只留在 `restart-gate.sh`,那裡只負責判斷新舊工作階段。 Who:`jsc-hooks` 的部署後強制重啟閘門(R15),以及它與 `session-timer.sh` 的清除分工。What:`README.md` 改三處——Hooks 表的 `restart-gate.sh` 與 `session-timer.sh` 兩列、覆蓋範圍的引言、以及「部署後重啟狀態檔」整節。整節改寫後寫明狀態檔路徑是 `$JSC_HOME/restart-required.d/{CLI 代號}`、一支 CLI 一份的理由、`report` 的輸出格式,以及舊檔相容與它可移除的時機。`skills/hooks-install/SKILL.md` 第 45 項改為 `restart-required.d/{cli}`,補上「每支 CLI 只讀自己那一份」、「重啟只清自己那一份」、「`report` 一支 CLI 一行」與舊檔過渡說明。 Why:這兩份是接線與判讀閘門時唯一會被讀到的說明。路徑改了而文件沒改,看文件的人會去看一個不存在的檔案,也會沿用「重啟一支就全清」的舊認知去判斷閘門有沒有生效。一支 CLI 一份的理由(並行覆寫與全域解除兩個實測缺陷)要留在文件裡,下一個人才不會為了少一個目錄又改回單一檔案。 How:只改說明,不動任何判定邏輯。路徑與格式的唯一來源仍是 `hooks/restart-gate.sh`,文件只轉述,舊檔相容的取捨與可移除清單也指回檔頭那一節,兩邊不各自維護一份。覆蓋範圍的引言照舊據實寫:非 claude 的四支 CLI 沒有 pre-tool hook,這道閘門一次都擋不下來,改成一支一份也不會變。 Who:`jsc-hooks` 的存取庫說明與 `jsc-hooks:hooks-install` 技能文件,對齊部署後重啟閘門的實作。