feat(role): SessionEnd 立即釋放角色鎖,不必等閒置逾時 #21

Merged
admin merged 2 commits from develop into master 2026-07-29 04:49:18 +00:00
Member

問題

關掉 CLI 後立刻重開新工作階段時,角色會被自己上一個階段的殘留鎖擋住,最久要等 30 分鐘才叫得回來。

原本只有兩條釋放路徑:持有者 transcript 閒置超過 ROLE_INSTANCE_IDLE_MINUTES(預設 30 分鐘),或手動 role_sleep.sh --unlock。剛關閉的 transcript mtime 還很新,機制無法區分「已關閉」與「正在別的視窗打字」,因此誤判為仍活躍。

實測數據:

持有者 transcript 最後寫入 = 12:38:30
新階段啟動                  = 12:40:07  (約 1.5 分鐘後)
find -mmin +30              → 無輸出(判定「仍活躍」)

解法

新增 SessionEnd hook(scripts/role/role_unload.sh)作為快速路徑,工作階段正常結束時立即刪鎖。

mtime 閒置逾時後援刻意保留 —— SessionEnd 不保證觸發(kill -9、直接關終端機視窗、WSL 關機、當機都不會跑),少了後援會在異常結束時把角色鎖死到下次手動解鎖。兩條路徑缺一不可。

關鍵:只釋放屬於自己的鎖

被鎖擋下的第二個階段關閉時同樣會觸發 SessionEnd。若無條件刪鎖,它會把仍在使用中的第一個階段的鎖一起刪掉,等於讓整個單一實例限制形同虛設。因此只在鎖檔登記的 transcript 等於自己時才釋放。

變更檔案

檔案 變更
scripts/role/role_unload.sh 新增 — SessionEnd hook 主程式
hooks/hooks.json 註冊 SessionEnd(timeout 10s,沿用既有路徑搜尋樣板)
scripts/role/role_lib.sh 更新單一實例區塊註解:說明快速路徑+後援為何缺一不可
skills/role/SKILL.md 元件表/腳本表/支援表/hook 歸屬表/單一實例章節;frontmatter 補鎖相關觸發詞
README.md 目錄樹註解、元件適用範圍表
三份 manifest 0.0.60.0.7(依 spec-plugin-version,以 master 現行版 +1)

測試

以隔離的 ROLE_HOME 與模擬 hook JSON 實測六種情境,未動到實際 ~/.roles

情境 期望 結果
鎖是自己的 釋放
鎖屬於別的階段 不釋放
hook 未提供 transcript 不釋放
目前無鎖 靜默 exit 0
ROLE_SKIP_INSTANCE_LOCK=1(sub agent) 不動鎖
ROLE_SINGLE_INSTANCE=0(限制停用) 不動鎖

未驗證項目:harness 實際送出 SessionEnd 事件的行為需離開一次工作階段才能確認,本 PR 僅驗證腳本邏輯。

合併後生效步驟

cache 依版本複製,需 claude plugin update 並重開工作階段後才生效。

## 問題 關掉 CLI 後立刻重開新工作階段時,角色會被自己上一個階段的殘留鎖擋住,最久要等 30 分鐘才叫得回來。 原本只有兩條釋放路徑:持有者 transcript 閒置超過 `ROLE_INSTANCE_IDLE_MINUTES`(預設 30 分鐘),或手動 `role_sleep.sh --unlock`。剛關閉的 transcript mtime 還很新,機制無法區分「已關閉」與「正在別的視窗打字」,因此誤判為仍活躍。 實測數據: ``` 持有者 transcript 最後寫入 = 12:38:30 新階段啟動 = 12:40:07 (約 1.5 分鐘後) find -mmin +30 → 無輸出(判定「仍活躍」) ``` ## 解法 新增 `SessionEnd` hook(`scripts/role/role_unload.sh`)作為**快速路徑**,工作階段正常結束時立即刪鎖。 **mtime 閒置逾時後援刻意保留** —— `SessionEnd` 不保證觸發(`kill -9`、直接關終端機視窗、WSL 關機、當機都不會跑),少了後援會在異常結束時把角色鎖死到下次手動解鎖。兩條路徑缺一不可。 ### 關鍵:只釋放屬於自己的鎖 被鎖擋下的第二個階段關閉時**同樣會觸發 `SessionEnd`**。若無條件刪鎖,它會把仍在使用中的第一個階段的鎖一起刪掉,等於讓整個單一實例限制形同虛設。因此只在鎖檔登記的 `transcript` 等於自己時才釋放。 ## 變更檔案 | 檔案 | 變更 | | --- | --- | | `scripts/role/role_unload.sh` | **新增** — SessionEnd hook 主程式 | | `hooks/hooks.json` | 註冊 `SessionEnd`(timeout 10s,沿用既有路徑搜尋樣板) | | `scripts/role/role_lib.sh` | 更新單一實例區塊註解:說明快速路徑+後援為何缺一不可 | | `skills/role/SKILL.md` | 元件表/腳本表/支援表/hook 歸屬表/單一實例章節;frontmatter 補鎖相關觸發詞 | | `README.md` | 目錄樹註解、元件適用範圍表 | | 三份 manifest | `0.0.6` → `0.0.7`(依 spec-plugin-version,以 master 現行版 +1) | ## 測試 以隔離的 `ROLE_HOME` 與模擬 hook JSON 實測六種情境,未動到實際 `~/.roles`: | 情境 | 期望 | 結果 | | --- | --- | --- | | 鎖是自己的 | 釋放 | ✅ | | 鎖屬於別的階段 | 不釋放 | ✅ | | hook 未提供 transcript | 不釋放 | ✅ | | 目前無鎖 | 靜默 exit 0 | ✅ | | `ROLE_SKIP_INSTANCE_LOCK=1`(sub agent) | 不動鎖 | ✅ | | `ROLE_SINGLE_INSTANCE=0`(限制停用) | 不動鎖 | ✅ | **未驗證項目**:harness 實際送出 `SessionEnd` 事件的行為需離開一次工作階段才能確認,本 PR 僅驗證腳本邏輯。 ## 合併後生效步驟 cache 依版本複製,需 `claude plugin update` 並重開工作階段後才生效。
jiantw83 added 2 commits 2026-07-29 04:48:29 +00:00
關掉 CLI 後立刻重開新階段時,角色會被自己上一個階段的殘留鎖擋住:
原本只有「持有者 transcript 閒置超過 30 分鐘」與手動 --unlock 兩條釋放
路徑,而剛關閉的 transcript mtime 還很新,機制無法區分「已關閉」與
「正在別的視窗打字」,因此最久要等 30 分鐘才叫得回角色。

新增 SessionEnd hook(role_unload.sh)作為快速路徑,工作階段正常結束時
立即刪鎖。mtime 閒置逾時後援保留不動 —— SessionEnd 不保證觸發
(kill -9、直接關終端機、WSL 關機、當機都不會跑),少了後援會在異常
結束時把角色鎖死到下次手動解鎖,兩條路徑缺一不可。

只在鎖檔登記的 transcript 等於自己時才釋放:被鎖擋下的第二個階段結束時
同樣會觸發 SessionEnd,若無條件刪鎖會把仍在使用中的第一個階段的鎖一起
刪掉,等於讓單一實例限制形同虛設。無法取得 transcript、sub agent
(ROLE_SKIP_INSTANCE_LOCK=1)、限制已停用(ROLE_SINGLE_INSTANCE=0)
一律不動鎖。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
admin approved these changes 2026-07-29 04:49:15 +00:00
admin merged commit 9bf32a21d2 into master 2026-07-29 04:49:18 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: plugins/shared#21