From ccb28e2bb3cf0427db9eb8000352b7a4e3acb18b Mon Sep 17 00:00:00 2001 From: Jeffery Date: Mon, 7 Sep 2026 08:59:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=9F=B7=E8=A1=8C):=20=E5=88=B0=E6=9C=9F?= =?UTF-8?q?=E6=B8=85=E5=96=AE=E7=9A=84=E8=B7=AF=E5=BE=91=E4=B8=80=E5=BE=8B?= =?UTF-8?q?=E7=94=B1=E5=91=BC=E5=8F=AB=E7=AB=AF=E9=A4=B5=E9=80=B2=E4=BE=86?= =?UTF-8?q?=EF=BC=8C=E4=B8=A6=E6=8B=92=E8=B7=91=E9=81=8E=E8=88=8A=E7=9A=84?= =?UTF-8?q?=E6=B8=85=E5=96=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 判到期與執行是兩支腳本,中間靠一個檔案交棒。判到期那一支是被巡檢用 --out 叫的,輸出寫進那一輪自己的暫存目錄;而執行那一支留了一個預設路徑,指向助理 狀態目錄底下那一份。兩邊不是同一個位置。 於是執行那一支從上線到現在,每一輪讀的都是「上一次有人手動跑判定留下的那一 份」。實測那份清單在機器上放了 67 小時,每一輪都被拿去動手,其中一輪還跑了 一筆已經被移除的待辦——那一筆的識別碼在重新種入時換過了。 而它看起來完全正常。那份舊清單產生的當下是對的,內容剛好沒變,所以每一輪的 報告都合理。錯了 67 小時才顯形。 兩處改動。判到期那一步本來只把給人看的那個檔案轉出去,機器可讀的那一份被 吞掉了,現在一起印成 due_rows_file=。執行那一支拿掉預設值:少帶那個選項就 回用法錯誤,吵一次總比安靜地對舊資料動手好。 另外加一道時效保護。判到期與執行之間隔著呼叫端,那一段有可能斷掉——判定失敗 了而那一輪照樣往下走,或呼叫端餵進上一輪的路徑。門檻取心跳的過期門檻:那是 這台機器認定「一輪跑完的紀錄還算新鮮」的長度,一份比它還舊的到期清單本來就 不可能是這一輪產生的。讀不到門檻就退回 300 秒,跟心跳那一支的預設一致。 實測:不帶選項回 6,清單不存在回 2,兩小時前的清單回 2 且訊息帶出實際秒數與 門檻,新的空清單回 0;拿真機那份 67 小時前的舊清單餵進去,確實被擋下。 Co-Authored-By: Claude Opus 5 --- tools/patrol.sh | 7 +++++++ tools/run-due.sh | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tools/patrol.sh b/tools/patrol.sh index 9ba9ef3..b67f789 100755 --- a/tools/patrol.sh +++ b/tools/patrol.sh @@ -205,6 +205,7 @@ WARN=0 DUE_STATUS=fail DUE_RC=0 DUE_MD='' +DUE_ROWS='' DUE_NOTE='' DUE_TASKS='' DUE_EVENTS='' @@ -912,6 +913,11 @@ due_scan() { JSC_HOME="$JSC_HOME" sh "$_due" scan --out "$RD/due" >"$RD/due.out" 2>"$RD/due.err" /dev/null | head -n1) + # 機器可讀的那一份也要轉出去。判到期與執行是兩支腳本,中間靠這個檔案交棒;不轉出去 + # 呼叫端就只能靠預設路徑猜,而那個預設指向的是上一次有人手動跑 scan 留下的檔案。 + # 實測踩過:那份舊清單在機器上放了六小時,執行那一支每一輪都對它動手,跑的是一筆已經 + # 被移除的待辦,而且看起來完全正常——舊清單當時是對的,內容剛好沒變。 + DUE_ROWS=$(sed -n 's/^rows_file=//p' "$RD/due.out" 2>/dev/null | head -n1) DUE_TASKS=$(sed -n 's/^tasks_due=//p' "$RD/due.out" 2>/dev/null | head -n1) DUE_EVENTS=$(sed -n 's/^events_new=//p' "$RD/due.out" 2>/dev/null | head -n1) case "$_rc" in @@ -1157,6 +1163,7 @@ case "$CMD" in printf 'due_status=%s\n' "$DUE_STATUS" printf 'due_rc=%s\n' "$DUE_RC" printf 'due_file=%s\n' "$DUE_MD" + printf 'due_rows_file=%s\n' "${DUE_ROWS:-}" printf 'pending=%s\n' "$PEND_COUNT" printf 'events_total=%s\n' "$EV_TOTAL" printf 'events_bad=%s\n' "$EV_BAD" diff --git a/tools/run-due.sh b/tools/run-due.sh index 56d28a6..c6429bc 100755 --- a/tools/run-due.sh +++ b/tools/run-due.sh @@ -95,7 +95,19 @@ case "$JSC_HOME_RESOLVED" in /*) ;; *) die 6 'JSC_HOME 與 HOME 都解不出絕對路徑,找不到助理狀態目錄。' ;; esac -[ -n "$ROWS" ] || ROWS="$JSC_HOME_RESOLVED/assistant/due/rows.txt" +# --rows 沒有預設值,一律要指定。 +# +# 原本有預設,指向助理狀態目錄底下那一份。那個預設是這一支上線之後最嚴重的一個缺陷: +# 判到期那一支是被巡檢用 --out 叫的,輸出寫進那一輪自己的暫存目錄,跟這個預設不是同一個 +# 位置。於是這一支每一輪讀的都是「上一次有人手動跑 scan 留下的那一份」。實測那份清單在 +# 機器上放了六小時,這一支每一輪都對它動手,還跑了一筆已經被移除的待辦。 +# 而它看起來完全正常——那份舊清單產生的當下是對的,內容剛好沒變。**錯了六小時才顯形。** +# +# 所以不留預設。少帶這個選項就回用法錯誤,吵一次總比安靜地對舊資料動手好。呼叫端要拿 +# collect 印的 due_rows_file= 餵進來,那是同一輪產生的那一份。 +if [ -z "$ROWS" ]; then + die 6 '沒有帶 --rows。這一支不猜到期清單的位置:判到期那一支的輸出寫在叫它的人指定的目錄裡,猜一個預設就會讀到別人留下的舊檔案,而舊檔案讀起來跟新的一模一樣。請把 collect 印的 due_rows_file= 餵進來。' +fi [ -f "$ROWS" ] || die 2 "到期清單讀不到:$ROWS。請先跑 due.sh scan——沒跑過判定,跟「都沒到期」不是同一件事。" @@ -121,6 +133,26 @@ find_tool() { # $1=domain $2=相對路徑 return 1 } +# 清單太舊就拒跑。判到期與執行之間隔著呼叫端,那一段有可能斷掉——collect 失敗了而那一輪 +# 照樣往下走,或呼叫端餵進上一輪的路徑。兩種都會讓這一支對著一份不描述現況的清單動手, +# 而那正是它上線之後六小時裡在做的事。 +# 門檻取心跳的過期門檻:那是這台機器認定「一輪跑完的紀錄還算新鮮」的長度,一份比它還舊的 +# 到期清單本來就不可能是這一輪產生的。讀不到門檻就退回 300 秒,跟心跳那一支的預設一致。 +_ttl='' +if _hb=$(find_tool hooks hooks/heartbeat.sh 2>/dev/null); then + _ttl=$("$_hb" report 2>/dev/null | sed -n 's/.*[[:space:]]ttl=\([0-9]*\).*/\1/p' | head -n1) +fi +case "${_ttl:-}" in + ''|*[!0-9]*) _ttl=300 ;; +esac +_mtime=$(date -r "$ROWS" +%s 2>/dev/null) || _mtime='' +if [ -n "$_mtime" ]; then + _age=$((NOW - _mtime)) + if [ "$_age" -gt "$_ttl" ]; then + die 2 "到期清單是 $_age 秒前產生的,超過門檻 $_ttl 秒,這一支不對它動手:$ROWS。一份比心跳門檻還舊的清單不可能描述這一輪,照著跑會動到已經不存在的待辦。請確認判到期那一步這一輪真的跑成功了,再把它印的路徑餵進來。" + fi +fi + TASKS_SH="$HERE/tasks.sh" [ -f "$TASKS_SH" ] || TASKS_SH=$(find_tool assist tools/tasks.sh) \ || die 2 '找不到 tasks.sh,成敗沒有地方回寫。待辦簿只有一個寫入者,缺了它這一輪不該跑。'