fix(schedule): 排程條目不再寫死工作階段代號,CLI 判定跟上共用函式

兩個坑,都在同一支腳本,都讓無人值守那一輪看起來正常卻不正常。

一、條目寫死了工作階段代號。那一輪的兩半因此落在不同的值上:start 由 hook
寫,它從 hook 的標準輸入 JSON 讀得到 CLI 真正的代號;end 由工具腳本寫,沒有
那份 JSON,只能讀環境變數,於是讀到寫死的那個字串。配對的判準是工作階段
代號加技能名,兩半的代號不同就永遠配不起來。

這台機器上實測累積了 91 筆假的「只有 start 沒有 end」,其中 90 筆是同一支
每輪都會被叫用的技能。逐個工作階段數下來:92 個是一個 start 零個 end,另有
一個工作階段收著 29 個 end 卻一個 start 都沒有——那一個就是寫死的代號。

代價不只是數字難看。那一節存在的理由正是「只有 start 沒有 end 是一支技能
中止的唯一證據」,而這批假訊號只增不減,遲早把真的中止蓋掉。不設這個變數,
兩半就都落在 CLI 自己給的代號上,實測驗過確實配得起來。

二、CLI 判定抄了一份較窄的。共用函式認三個環境標記,這一份只認第一個,而
工具腳本被叫用時那一個不會設。於是每一次不帶 --cli 的安裝都回用法錯誤——
技能本文的啟動步驟叫的正是不帶 --cli 那一種,這台機器上一定裝不起來。同一個
判斷寫兩份就是這樣漂走的:共用函式後來補了兩個標記,這一份沒跟上,而漂移
的代價是一個看起來像「這台機器沒裝 CLI」的錯誤。

殘留的那 91 筆不必手動清,那一支本來就會把開超過一天的丟掉,一天內收斂。

三份 manifest 版號 0.2.1 升到 0.2.2。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 12:29:20 +08:00
co-authored by Claude Opus 5
parent eade09bf62
commit c76e8489a6
5 changed files with 22 additions and 8 deletions
+18 -4
View File
@@ -303,7 +303,15 @@ patrol_command() {
[ -n "$PATROL_CMD" ] && { printf '%s' "$PATROL_CMD"; return 0; }
_cli="$CLI"
[ -n "$_cli" ] || _cli="${JSC_CLI:-}"
[ -n "$_cli" ] || { [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && _cli=claude; }
# 認得出 claude 的三個標記要跟 jsc-hooks 的 cli_name() 一模一樣。這裡本來只認第一個,
# 而工具腳本被叫用時那一個不會設,於是每一次不帶 --cli 的安裝都回用法錯誤——技能本文
# 的啟動步驟叫的正是不帶 --cli 那一種。同一個判斷寫兩份就是這樣漂走的:共用函式後來
# 補了兩個標記,這一份沒跟上,而漂移的代價是一個看起來像「這台機器沒裝 CLI」的錯誤。
# 共用函式加新標記時,這裡要一起加。
if [ -z "$_cli" ] && { [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] || [ -n "${CLAUDECODE:-}" ] \
|| [ -n "${CLAUDE_CODE_SESSION_ID:-}" ]; }; then
_cli=claude
fi
case "$_cli" in
claude) _bin='claude'; _args="-p \"/jsc-assist:assistant 跑一輪巡檢 $ROOT_TOKEN\"" ;;
codex) _bin='codex'; _args="exec '\$assistant 跑一輪巡檢 $ROOT_TOKEN'" ;;
@@ -342,8 +350,14 @@ shq() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"; }
# 寫 NAME='' 進去,讀的人分不出是「刻意設成空」還是「安裝時忘了設」。
# JSC_GITEA_CONFIRM=yes 是必要的:寫入確認只認 tty,排程那一輪沒有 tty,不帶這個旗標監控頁
# 一定寫不成,而頁寫不成就不寫心跳,排程等於永遠空轉。
# 刻意不寫死工作階段代號。寫死的話那一輪的兩半會落在不同的值上:start 由 hook 寫,它從 hook
# 的標準輸入 JSON 讀得到 CLI 真正的代號;end 由工具腳本寫,沒有那份 JSON,只能讀環境變數,
# 於是讀到寫死的那個字串。兩半的代號不同就永遠配不起來,每一輪都被算成一支中止的技能,
# 而那個數字只增不減,最後把真的中止蓋掉——那一節存在的理由正是「只有 start 沒有 end 是
# 中止的唯一證據」。不設這個變數,兩半就都落在 CLI 自己給的代號上。
# 2026-09-04 實測:一台機器累積了 91 筆假中止,其中 90 筆是同一支被每輪叫用的技能。
env_prefix() { # 順便把快照到的變數名稱寫進 $1,供輸出列出名稱(只有名稱,不含值)
_p='JSC_CLI=cron JSC_SESSION_ID=schedule JSC_GITEA_CONFIRM=yes'
_p='JSC_CLI=cron JSC_GITEA_CONFIRM=yes'
_names=''
for _n in $(snapshot_names); do
eval "_v=\${$_n:-}"
@@ -364,7 +378,7 @@ mask_secret() { sed "s/GITEA_TOKEN=[^ ]*/GITEA_TOKEN='***'/g"; }
cron_entry() { # $1=工作代號
_spec=$(spec_of "$1")
case "$1" in
heartbeat) _cmd="JSC_CLI=cron JSC_SESSION_ID=schedule '$HEARTBEAT' write" ;;
heartbeat) _cmd="JSC_CLI=cron '$HEARTBEAT' write" ;;
patrol) _cmd="$ENV_PREFIX $PATROL_RESOLVED" ;;
esac
printf '%s %s </dev/null >>%s 2>&1 %s' \
@@ -445,7 +459,7 @@ trap 'rm -rf "$TMPD"' EXIT
# 巡檢指令與環境快照在這裡就解出來。放進 cron_entry 再解的話,那支是在命令替換的子行程裡
# 跑,判不出 CLI 時 die 只結束子行程,主流程會帶著空指令繼續往下裝。
PATROL_RESOLVED=''
ENV_PREFIX='JSC_CLI=cron JSC_SESSION_ID=schedule JSC_GITEA_CONFIRM=yes'
ENV_PREFIX='JSC_CLI=cron JSC_GITEA_CONFIRM=yes'
SNAPSHOT_NAMES=''
case "$CMD:$JOBS" in
install:*patrol*)