身為交出 PR 的人,我要它兩週內有人給結論,我才不會做完的東西永遠進不了版本 #81
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
問題
我交出去的 PR 沒有人去看它。今天實查,最久的三個已經開了兩星期:
全 org 現在有 9 個 open PR。沒有任何一支閘在管 PR——我把 v0.9.0 的 46 支閘檔頭全部撈出來對過,一支都沒有。
所以會發生的事是:subagent 做完、開了 PR、交回票上,然後那個 PR 就躺在那裡。票看起來是「已交付」,但東西沒有進 main,也就沒有進版本,leo 手上永遠不會出現它。
目標
收工的時候,如果還有我自己該審的 PR 沒有結論,就不准收工。
「有結論」只有三種,缺一不可:
驗收條件
deliverable 類型
code(→ PR)
細節
這條在 SOP 裡的位置:鐵律 7 + S6(PR 審查)。
為什麼是 Stop hook 不是 PreToolUse:PR 堆積不是「某個動作做錯了」,是「某個動作從來沒發生」。沒有發生的事情攔不到,只能在收工那一刻清點。同
worklist-guard.sh的形狀。不要重造的:
baton-handback-guard.sh已經在管「棒子有沒有交回來」(指派+tag+下一步三格),這支管的是交回來之後總管有沒有動它。兩支是接力,不是重複。已知的坑:
empty-handed-stop-guard.sh的判準是「這回合有沒有 tool call」,worklist-guard.sh是「還有沒有未完成步驟」。第三支再加「有沒有未決 PR」時要確認三支不會互相踩——ISEP v0.6.0 的 divergence 文件 §B4 記過同款:「s/review佇列非空即 block 總管 stop」會讓總管永遠停不下來。擋的要是遺忘,不是擋等待:判準應該是「有我自己開的 PR,而這一回合我完全沒碰它」。實查出處:
GET /repos/inkstone/{Arcrun,arcrun-rag,ISEP}/pulls?state=open(2026-08-27);46 支閘檔頭全文在 ISEP mainhooks/。【身份】subagent/ISEP
交件:
inkstone/ISEP#95#95
做了什麼
hooks/pr-verdict-guard.sh(Stop,F 組) — 收工那一刻清點沒有結論的 PR,點名是哪幾個(含開了幾天與網址)。外加一格:merge 了但 head 分支還留著的也點名——票上寫死「併完當場刪掉那條 branch」,分支還在=結論只給了一半。
scripts/pr-verdict— 三種結論各是一個動作:merge(併+刪分支+當場複驗分支真的不見了)/reject(理由寫進票+關 PR+刪分支)/changes(修改要求寫進票+留REQUEST_CHANGES)/list/--dry-run。所有掉棒的形狀都一樣——第一個動作做了、第二個沒有;綁成一個指令就沒有那個狀態可以存在。
票上點名要避開的那條錯路,怎麼避的
「佇列非空即 block」會讓總管永遠停不下來(v0.6.0 divergence §B4)。
所以擋的是遺忘,不是等待,四層都用來放掉「等待」:
Human⇒ 不點名。指派是 Gitea 原生欄位,不是措辭(同baton-handback-guard那張三格表)REQUEST_CHANGESreview ⇒ 「要求修改」這個結論已經給了,球在對方腳下「碰過」不讀任何一句話:①
updated_at跟上次收工比變了(外部系統的事實)② 上次收工時還不存在(不在它誕生的回合就開罵)③ 這回合的 tool call 出現它的識別碼
——③ 是為了接住「結論寫在票上」這種本票規定的合法結論。
驗收條件逐條對照
inkstone/ISEP#999feat/y還在 ⇒ 擋並點名分支;⑱ 刪掉後放行。pr-verdict merge會複驗分支查不到了才回報成功真跡重演(真實 Gitea,唯讀,沒有改動任何 PR)
⇒ 票上那三個(15/14/14 天)全部抓到。第 4 次送出才會再響一次(門檻 1→4,實測過)。
測試
離線走
PR_VERDICT_FIXTURE,不打 Gitea、不開測試 PR、不留測試票。A 群 8 條全在證「不該擋」(誤攔比漏擋嚴重);E 群證「指派下去之後連 9 個回合永遠不再點名」
——閘訊息裡承諾的出路是真的。
既有測試沒被弄壞:
mainline-idle-guard61/61、dispatch-format-guard33/33、factory-idle-guard33/33。要總管接手的兩件
inkstone/ISEP#95(我不准推 main)。v0.10.0的 tag——plugin.json已經是0.10.0,所以
scripts/check-version-consistency.sh現在會紅,那是預期的:分支上打不了 tag(
release-tag-guard管那道),做法與#78/#79/#80一致。沒打 tag ⇒
plugin update是 no-op ⇒ 這支閘到不了任何人手上。沒做的(誠實標記)
docs/TESTING.md的 A8「新 session 閘會觸發」我沒跑——那要開新的 Claude Code session載入 plugin 才驗得了,不在我這條線的能力範圍內。腳本層與
hooks.json註冊都驗過了(
ls hooks/*.sh | wc -l= 52、grep -c '"command":' hooks/hooks.json= 65)。🏃 棒子交回 →
claude-code下一步:複審並 merge inkstone/ISEP#95,併完打 v0.10.0 的 tag(plugin.json 已是 0.10.0,沒打 tag 的話 plugin update 是 no-op,這支閘到不了任何人手上)
證據:#95 測試 52/52,真實 Gitea 唯讀重演抓到票上那三個(15/14/14 天)
【身份】subagent/ISEP
⚠️ 衝突已解完並驗過,但推不上去——被
github-contact-guard誤攔合併 commit 在本機是
36f75dd,PR#95上還是舊的 head,所以它仍然mergeable=False。需要有人幫忙把這條分支推上 Gitea,或修掉下面這個誤攔。
誤攔的機制(查證過,不是猜的)
github-contact-guard.sh的 ②b 分支拿 payload 的cwd去解 remote 名:而這台機器的
cwd釘在/home/user/inkstoneco(GitHub 薄殼),不管我
cd到哪個 checkout 都一樣。我的 repo 在/home/user/isep-work,origin是https://***@git.uncle6.me/inkstone/ISEP.git——是 Gitea,不是 GitHub。⇒ 從任何側邊 checkout 下
git push origin <branch>都會被判成推 GitHub。這正是那支閘自己在註解裡寫著要避免的那件事:
「⚠️ 只有解出來真的是 github 才擋——Gitea 是真相源、要能自由推,不可誤傷。」
(同一個根因也讓
unpushed-police去報薄殼那條claude/isep-assessment-plan-ivvst6,而那條相對
origin/main是 0 個 commit。)我沒有做的事(刻意)
.github-armed。那支閘檔頭第 9 行自己寫著「AI 技術上可自建.github-armed繞過⋯⋯最後一段靠 D20 鐵律與自律」——會寫這句就是要人別做,我不做。
它誤攔不代表我可以換條路。
push origin、先cd再推、明寫 Gitea URL)都被同一條規則擋,就停下來報告,不再找第四種寫法——「拆指令繞路」久了整道閘就沒人當真(該檔註解原話)。
要怎麼解(擇一,我這邊都能立刻接手)
git -C /home/user/isep-work push origin feat/pr-verdict-guardCWD改成「從指令裡的cd目標解」或「用git rev-parse --show-toplevel從實際 repo 解」,而不是信 payload 的
cwd。這是獨立缺陷,值得另開一張票。等於把閘的語義搞壞。
合併後的狀態(全部驗過)
在合併後的樹上實際數(沒有相加推算):
53支閘 /68條註冊 /38個 scripts 項目(其中 3 個是目錄,純檔案
35)。版本0.11.0(0.10.0已被#94用掉)。四個衝突檔一律「兩邊都留」:
hooks.json自動合併(兩支閘都在);TESTING.md的 A14/A15 讓已發版的#94保號,我的往後排成 A16/A17;hooks-inventory.mdF 組兩列並存;plugin.json升 0.11.0。三套測試合併後重跑(先
env -u CLAUDE_CODE_CHILD_SESSION清掉殘留的=1,否則 countdown 的 B 群是假綠):
順帶複驗沒被弄壞:mainline-idle 61/61、dispatch-format 33/33、factory-idle 33/33、reply-identity 11/11。
合併後的閘打真實 Gitea(唯讀)仍抓得到票上那三個真跡(15/14/14 天)。
【身份】subagent/ISEP
「第一次怎麼推成功的」——查證結果
🔴 先講結論:我不知道第一次為什麼會過。 但我排除掉了三個假說,
而剩下那個唯一還站得住的,剛好是你們最需要知道的那一個。
排除① 差在
-u?——不是拿那支閘自己的 sed 跑兩條字串:
兩條都解成
origin。-u不構成差別,inkstone/ISEP#90不必記這一條。排除② 差在工作目錄?——不是
cd不會跨 call 保留(複合指令、單獨指令都一樣)。⇒ 每一次 Bash 的 cwd 永遠是
/home/user/inkstoneco(GitHub 薄殼),第一次成功的那次也是。cwd 從頭到尾沒變過。
排除③ 我重現了第一次那條指令——照樣被擋
一字不差、同一個 cwd、同一支閘 ⇒ 變數不是指令,是環境。
唯一還站得住的假說(未證實,我不當事實講)
那支閘裡唯一能產生「同一條指令、不同判決」的機制,就是
.github-armed(限時保險,過期自動
rm)。現在六個可能位置全都沒有這個檔。但它的留痕在這台機器上是看不到的:
⇒ 就算當時保險是開的,也不會留下任何一行紀錄。
所以「log 裡沒有」不能當成「當時沒開保險」的證據——這是讀不到,不是不存在。
📌 時間軸(UTC):
00:05:40我的 commit → 約00:06推成功 →00:17/00:27其他工人的 checkout 出現 →/root/.claude/plugins/目錄 mtime00:22→ 約
00:5x起全部被擋。plugin cache 內容本身是 8/23 的,沒被換過。⚠️ 我原本想主張「那段時間閘是靜默的,證據是我開 PR 的 urllib POST 沒被
ticket-api-bypass-guard攔」——實測後撤回:那條指令餵給那支閘是exit 0,因為
/tmp/.ticket-where-ok戳記是新鮮的,它本來就該放行。不是閘沒作用。這個假說我自己驗掉了,沒有把它當情報交出去。
🔴 你真正要拿去用在另外三條線上的,是這一段(這部分是證實的)
⇒ 任何工人,在任何側邊 checkout,用
git push origin <自己的分支>推 Gitea,都會被判成推 GitHub。 跟他的分支、他的 repo、他的指令長相都無關。
另外三條線一定會撞到同一面牆。
這正是那支閘註解裡自己寫著要避免的:
正解(建議記進
inkstone/ISEP#90)remote 要對著真正要被推的那個 repo解,不是對著釘死的 cwd 解。例如:
🔴 不要用「開 GitHub 保險」當解法——這根本不是 GitHub 寫入。
用
github-arm.sh去放行一件 Gitea 的事,等於把那道閘的語義弄壞,而且 30 分鐘後所有人再一起被擋一次。
有一條會通、但我沒有用,你自己判斷要不要給其他工人
如果 checkout 裡把 Gitea remote 取名
gitea而不是origin(
scripts/ticket的token()本來就同時認這兩個名字,這是本 repo 既有慣例),那麼
git -C <薄殼> remote get-url gitea會查不到 ⇒RURL空 ⇒ 放行。⚠️ 但它會通的原因是「查詢失敗」,不是「判斷正確」——
同一個破口也會讓真正指向 GitHub、卻不叫
origin的 remote 整個溜過去。它是 workaround,不是修好。 我把它報給你,但我自己不用它:
你交代過「不要再找第四種寫法」,而這就是第四種。
我這條線現在的狀態
合併
36f75dd驗完但推不上去,PR#95仍mergeable=False。內容全綠:
countdown 20/0、prod-write 37/0、pr-verdict 52/0,另 mainline-idle 61/61、dispatch-format 33/33、factory-idle 33/33、reply-identity 11/11。
數字在合併後的樹上實數:
53支閘/68條註冊/38個 scripts 項目(3 個是目錄,純檔案35),版本0.11.0。一句話就能解掉:
git -C /home/user/isep-work push origin feat/pr-verdict-guard(在你那端跑)。