本機複本一個 domain 一把鎖,重啟閘門不等整輪判定 #65

Merged
admin merged 1 commits from fix/serialize-shared-clone-and-always-gate into develop 2026-09-07 04:43:42 +00:00
Member

這一批修兩件事,同一輪實測踩到、同一個根因

剛才那一輪部署,claude 與 codex 的 result 都是 fail——而五支 CLI 的外掛全部裝好了。

一、五支平行跑,卻搶同一份本機複本

那一份複本一台機器只有一份($JSC_LOCAL_PLUGINS,預設 $JSC_HOME/plugins),而技能規定五支平行。平行本身是對的:它們寫的是不同的外掛目錄。但複本不是。

而且不只 antigravity 與 kiro 會碰它——check-requires.sh 從那裡讀每個 domain 的 manifest,所以五支都會 pull 同一批目錄,一輪 55 次。

實測撞出來的三個訊息:

Unable to create ORIG_HEAD.lock: File exists. Another git process seems to be running
cannot lock ref 'refs/remotes/origin/master': is at d5c4f92 but expected e77c0af
fatal: Need to specify how to reconcile divergent branches

改成 一個 domain 一把 mkdir 鎖——那是檔案系統這一層唯一原子的建立動作,兩支同時 mkdir 只有一支成功。等不到就印 warn 改用磁碟上的內容:別人正在拉同一份,硬等只是排隊。上一輪中途死掉留下的鎖用年紀判,門檻放寬到等待秒數的四倍。

複本已經在磁碟上而 pull 拉不動的那一種也改成只印 warn、不判整輪失敗。 內容在,只是可能比遠端舊。但一定要印出來——安靜地裝一份舊內容是這一組工具最怕的失效。clone 不存在那一種照舊算失敗:磁碟上根本沒東西可裝。

二、失敗那條路徑把重啟閘門一起跳過了

原本是這樣寫的:

if [ "$FAILED" -eq 0 ]; then
  mark_restart          # 只有成功才掛閘門
  printf 'result … ok'
fi
printf 'result … fail'

於是那一輪的 fail(原因是一條與部署無關的 git pull)讓 claude 沒有被掛上重啟閘門。五支裡四支有,唯一沒有的,剛好是正在跑那份剛被換掉的程式碼的那一支。

一道只在成功時才生效的提醒,在最需要它的那一次不會出現。

部分失敗代表磁碟上的外掛已經換了一部分,這時候更需要重啟,不是更不需要。改成 install 與 update 一律先掛,再判 result。

順帶

  • clip1:截訊息長度用的 cut -c 數的是位元組,多位元組字被切成兩半會留一個替代字元。截完過一次 iconv -c。
  • 乾跑一步都不動,連鎖都不取——取鎖是建目錄,那已經是寫入。我改完之後乾跑會真的去 pull,這一版修回來了。

實測

測什麼 結果
鎖沒人佔用 拿到,鎖目錄建起來
鎖被佔用 等滿次數後放棄,回「拿不到」
殘留的舊鎖(年紀超門檻) 搶回來
-n update claude jsc-assist git pull 印 exit -,沒有真的執行;跑完沒有留下任何 .lock-*
三支檢核 lint-scripts 10 支、check-behaviors 5 支、ste100-lint 全 0

邊界

真正的併發整合路徑這一輪沒有再實跑一次——那要再部署五支 CLI。鎖的邏輯是抽出來單獨驗的,乾跑驗過不會動任何東西。下一次真的部署就是它的第一次實測,那一輪的 warn 行數是要看的指標。

技能本文與行為清單都跟著改了:warn 多一種語意(本機複本這一輪沒有重新拉取),重啟閘門那一段寫明「整輪判定成 fail 也照寫」,並把這一次的實測寫進理由。

## 這一批修兩件事,同一輪實測踩到、同一個根因 剛才那一輪部署,claude 與 codex 的 `result` 都是 `fail`——**而五支 CLI 的外掛全部裝好了**。 ## 一、五支平行跑,卻搶同一份本機複本 那一份複本一台機器只有一份(`$JSC_LOCAL_PLUGINS`,預設 `$JSC_HOME/plugins`),而技能規定五支平行。平行本身是對的:它們寫的是不同的外掛目錄。**但複本不是。** 而且不只 antigravity 與 kiro 會碰它——`check-requires.sh` 從那裡讀每個 domain 的 manifest,所以五支都會 pull 同一批目錄,一輪 55 次。 實測撞出來的三個訊息: ``` Unable to create ORIG_HEAD.lock: File exists. Another git process seems to be running cannot lock ref 'refs/remotes/origin/master': is at d5c4f92 but expected e77c0af fatal: Need to specify how to reconcile divergent branches ``` 改成 **一個 domain 一把 `mkdir` 鎖**——那是檔案系統這一層唯一原子的建立動作,兩支同時 `mkdir` 只有一支成功。等不到就印 `warn` 改用磁碟上的內容:別人正在拉同一份,硬等只是排隊。上一輪中途死掉留下的鎖用年紀判,門檻放寬到等待秒數的四倍。 **複本已經在磁碟上而 `pull` 拉不動的那一種也改成只印 `warn`、不判整輪失敗。** 內容在,只是可能比遠端舊。但一定要印出來——安靜地裝一份舊內容是這一組工具最怕的失效。`clone` 不存在那一種照舊算失敗:磁碟上根本沒東西可裝。 ## 二、失敗那條路徑把重啟閘門一起跳過了 原本是這樣寫的: ```sh if [ "$FAILED" -eq 0 ]; then mark_restart # 只有成功才掛閘門 printf 'result … ok' fi printf 'result … fail' ``` 於是那一輪的 `fail`(原因是一條與部署無關的 `git pull`)讓 claude **沒有被掛上重啟閘門**。五支裡四支有,唯一沒有的,剛好是正在跑那份剛被換掉的程式碼的那一支。 **一道只在成功時才生效的提醒,在最需要它的那一次不會出現。** 部分失敗代表磁碟上的外掛已經換了一部分,這時候更需要重啟,不是更不需要。改成 `install` 與 `update` 一律先掛,再判 `result`。 ## 順帶 - `clip1`:截訊息長度用的 `cut -c` 數的是位元組,多位元組字被切成兩半會留一個替代字元。截完過一次 `iconv -c`。 - **乾跑一步都不動,連鎖都不取**——取鎖是建目錄,那已經是寫入。我改完之後乾跑會真的去 `pull`,這一版修回來了。 ## 實測 | 測什麼 | 結果 | | --- | --- | | 鎖沒人佔用 | 拿到,鎖目錄建起來 | | 鎖被佔用 | 等滿次數後放棄,回「拿不到」 | | 殘留的舊鎖(年紀超門檻) | 搶回來 | | `-n update claude jsc-assist` | `git pull` 印 `exit -`,沒有真的執行;跑完沒有留下任何 `.lock-*` | | 三支檢核 | `lint-scripts` 10 支、`check-behaviors` 5 支、`ste100-lint` 全 0 | ## 邊界 **真正的併發整合路徑這一輪沒有再實跑一次**——那要再部署五支 CLI。鎖的邏輯是抽出來單獨驗的,乾跑驗過不會動任何東西。下一次真的部署就是它的第一次實測,那一輪的 `warn` 行數是要看的指標。 技能本文與行為清單都跟著改了:`warn` 多一種語意(本機複本這一輪沒有重新拉取),重啟閘門那一段寫明「整輪判定成 fail 也照寫」,並把這一次的實測寫進理由。
jiantw83 added 1 commit 2026-09-07 04:35:58 +00:00
實測踩到兩件事,同一輪、同一個根因。

那一份本機複本一台機器只有一份,而技能規定五支 CLI 平行部署——平行是對的,
它們寫的是不同的外掛目錄。但複本不是:五支都會來 pull 同一個目錄,連只需要
讀 manifest 的那幾支也會(相依檢查從那裡讀)。git 對同一個存取庫的併發寫入
沒有保護,於是同一輪裡兩支撞在一起,一支拿不到 ORIG_HEAD.lock、一支的遠端
refs 換不上去。

後果是最難查的那一種:兩支的整輪判定都變成 fail,而外掛其實全部裝好了——
報告說失敗、實際成功,而真正的原因跟部署無關。

改成一個 domain 一把 mkdir 鎖:那是檔案系統這一層唯一原子的建立動作。等不到
就印一行 warn 改用磁碟上的內容,別人正在拉同一份,硬等下去只是排隊。上一輪
中途死掉留下的鎖用年紀判,門檻放寬到等待秒數的四倍。

複本已經在磁碟上而 pull 拉不動的那一種,也改成只印 warn、不判整輪失敗:
內容在,只是可能比遠端舊。但一定要印出來——安靜地裝一份舊內容,是這一組
工具最怕的那種失效。clone 不存在那一種照舊算失敗,磁碟上根本沒東西可裝。

第二件事更嚴重。原本的寫法是「整輪判定成功才掛重啟閘門」,於是那一輪的
fail 把閘門一起跳過了:外掛換了一半,而唯一沒有被告知要重啟的,剛好就是
正在跑那份剛被換掉的程式碼的那一支 CLI。一道只在成功時才生效的提醒,在最
需要它的那一次不會出現。改成 install 與 update 一律先掛,再判 result。

順帶補一支安全截斷:訊息截長度用的是 cut -c,那數的是位元組,多位元組字
剛好被切成兩半會留一個替代字元,而亂碼不影響結束碼、沒有人會來報。

乾跑那一路一步都不動,連鎖都不取——取鎖是建目錄,那已經是寫入。原本改完
之後乾跑會真的去 pull,這一版修回來了。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
admin merged commit 62ea8022e0 into develop 2026-09-07 04:43:42 +00:00
admin deleted branch fix/serialize-shared-clone-and-always-gate 2026-09-07 04:43:42 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: plugins/cli#65