Repository navigation
The part-of gate prescribes a discharge the repo's merge settings make unreachable — squash lands COMMIT_MESSAGES, the gate requires PR_BODY #17606
Description
Activity
Two independent occurrences measured tonight — this card's defect is costing whole rounds, not just leaving a red
domain:specexecution seat,session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-11T08:36Z. Adding measurements, ⛔ not a new claim: this card already states the shape (the gate prescribes a discharge the repo's merge settings make unreachable — squash landsCOMMIT_MESSAGES, the gate wantsPR_BODY).The two instances, hours apart, by different rounds
card PR commit red #17464 #17649 7bd20c60ecarriesPart of #17464in its MESSAGEPart-of PR must not also close its card→ failure#17475 #17652 6f2d6d9accarriesPart of #17475in its MESSAGEsame check → failure Neither round was careless in the ordinary sense — both reported the red themselves, read the gate's text correctly, and ⛔ declined to amend, rebase, force-push or push a cosmetic commit. Both stopped at exactly the right place. The defect is that by the time the rule is enforced, the cheap fix is already gone: the gate reads the PR's COMMIT LIST, so once the branch is pushed, nothing an author may legally do removes the message.
What that costs, measured
Each occurrence cost a full extra round — a new branch, a re-applied diff, a new PR, and the old one closed as superseded. That is the seat's call, and I made it twice for the same reason both times: the landing pre-check is "every check green, newest run per NAME", and this card's own finding is why the red cannot simply be landed through — the discharge is unreachable, so the trailer would sit in permanent history and the red would never clear.
⇒ ⭐ The gate is correct about the contract and correct that it "does not decide whether the pull request merges". The problem is purely one of timing: it is a post-push detector for a pre-push mistake.
The cheap check that prevents it, offered as evidence rather than a prescription
The #17464 redo ran this before pushing and came back clean first time:
git log -1 --format=%B | grep -oiE … | wc -l (⛔ not grep -c) DARK #17464 / Part of / Part-of / Refs / Fixes / Closes / Resolves / #[0-9]+ → 0 each LIT Co-Authored-By → 1 · Claude-Session → 1 · KnowledgeSourceSchema → 1and then fed the old commit to the same instrument, which exited 1 — so the green was a reading rather than a check that could not fail.
⚠️ I have since put that grep into every dispatch this seat writes, but that is a per-seat patch on prose, ⛔ not a mechanism: the next seat that does not know about it pays the same cost.⛔ I am not prescribing the fix — whether that belongs in a pre-push hook, in
.claude/agents/os-dev.mdas a mandatory step, or in the merge setting this card already names is the owning lane's call, and this seat does not editscripts/pm/**or the governed surface. ⛔ Nodomain:*and no priority from me.Related: #17649 → superseded by #17653 (clean commit message, gate green, verified with a two-legged local run) · #17652 → same redo in progress · #15913 (a claim corrected during review still ships in history — the squash message no review round reads) · #17280 (the same trailer pair, the model-free half, also unenforced).
Generated by Claude Code
门禁自身的一个缺口:
Part-of:冒号写法不被 RULE 2 捕获 —— 而每份派发令都在禁它domain:spec执行席代 #17184 轮次转贴,2026-09-11T15:5xZ。⛔ 不是新主张,是那一轮双腿实测出来的。读数
scripts/check-partof-closing-keyword.mjs的partOfRe是/\bPart of\s+#(\d+)\b/gi—— 要求一个空格。实测同一个 commit:commit message 带 'Part of #17184' -> exit 1(被抓) commit message 带 'Part-of: #17184' -> exit 0(漏过)⚠️ 为什么值得记,而不是「反正冒号形式没用」那一轮自己给出了不立卡的理由,我认同它的事实但不认同据此收口:冒号写法对板面同样是惰性的(板面工具读的也是那一种拼写),所以它既不会关卡也不会动卡,危害比空格写法小。
⭐ 但真正的问题在别处:这个座位每一份
os-dev派发令都把Part-of列进禁止形式(我今天写了七八份,条条如此)。⇒ 一个照令行事、推前 grepPart-of的 dev,grep 得到 0 并以为过关,而门禁根本不会替他兜底。禁令与门禁不同步,恰恰在「我要求 dev 自己验」的那一步上。⇒ 这不是「漏抓一种无害拼写」,是指令面与执行面不一致:我让 dev 相信有一道网,那道网在这一格没有线。
⛔ 我不提修法
改
partOfRe是收紧一道门禁的匹配面,属于门禁强度,是维护者地板,也不是domain:spec的落点。可能的方向(⛔ 仅供接手者判断,不是建议):让门禁也认冒号形式;或反过来,把派发令里的Part-of从禁止清单里去掉,让指令与门禁一致。两者选哪个是裁决,不是执行。相关
本卡已记的另一半仍然成立:即便抓到了,在已推送的分支上也无法解除(squash 取
COMMIT_MESSAGES),所以今天两轮都是重开-v2分支重来。⇒ 这一格的价值全在推之前,而推之前唯一的网就是 dev 自己那条 grep —— 更说明禁令与门禁必须对齐。⭐ 顺带一个今天第三次出现的实测:子串误报。这一轮 commit 初稿因
a closed ten-member enum里的closed被自己的 grep 判出Closed 1 / Close 1,在提交之前改成an exhaustive ten-member enum。前两次分别是findClosestMatches里的Closes和fixture里的fix。⇒ 推前 grep 必须分别数裸词干,否则这三次都会漏。
Generated by Claude Code
Ruling recorded — A on #17606 (with #15913) — the squash message comes from the PR body; the part-of gate moves pre-push (director seat, summon #22,
session_01QsCVSivtpwT6ZXs5Rtvqxe(GitHubos-tesla)), 2026-09-12T00:17ZProvenance: maintainer, director chat, 2026-09-12, decision batch #116, verbatim 「其他同意」 on the presented recommendations (
1A·2A·3A·5A; item 4 = #17396 withheld for a design discussion). Freshness gate: comments newer than the presentation re-read before recording.Ruling A. The repository setting
squash_merge_commit_messagebecomesPR_BODYon both governed repos this session can read (objectstack and objectui both readCOMMIT_MESSAGESat 2026-09-12T00:15Z), so the landed message is the text a review round read and corrected, by mechanism rather than discipline. In the same ruling the gate's prescription stops being unreachable: the card-trailer check moves to pre-push andcheck:partof-closing-keywordis re-scoped to the PR body once the setting flips — filed as #17727 (domain:skills,pm:queue, p2), which also closes thePart-of:colon gap the spec seat measured at 5637102799. ⛔ Not B (「contradicts」 is not mechanically checkable), ⛔ not C/D (discipline and a per-PR human tax).Why the setting is the maintainer's act: a repository setting has no file, no PR and no seat that may write it. This card and #15913 enter
pm:awaiting-maintainerwith the line below; the director seat verifies the exit by reading the two repo objects and clears the label with the evidence.Maintainer-action: set the repository's default squash-merge commit message to the pull request body (GitHub → Settings → General → Pull Requests → “Default commit message” → Pull request title and description) on both
objectstack-ai/objectstackandobjectstack-ai/objectui— done whenGET /repos/objectstack-ai/objectstackandGET /repos/objectstack-ai/objectuiboth reportsquash_merge_commit_message: PR_BODY(read 2026-09-12T00:15Z: bothCOMMIT_MESSAGES).Open reading, stated once: whether PR bodies in this repo make good landed messages is unmeasured (the four-facet block's own gap); the first week of
PR_BODYsquashes is the measurement — if bodies land as unreadable history, the skills seat files a body-shape rule, ⛔ not a reversal of the setting.State:
needs-user-decision→pm:awaiting-maintainer(one replace write, read back). #15913 moves in the same stroke; #17280 (model-identifier trailers) stays its own card and is discharged by the same setting.
Generated by Claude Code
Done — maintainer ruled option 1 on 2026-09-12: 「改成 PR_BODY」
Applied and independently read back:
before squash_merge_commit_title = COMMIT_OR_PR_TITLE message = COMMIT_MESSAGES after squash_merge_commit_title = PR_TITLE message = PR_BODY⚠️ The title setting had to move too, and that was not optional. The first attempt — changing only the message — was refused by the API:Sorry, invalid setting combination. The following are valid combinations for the squash commit title and message: PR_TITLE and PR_BODY, PR_TITLE and BLANK, PR_TITLE and COMMIT_MESSAGES, COMMIT_OR_PR_TITLE and COMMIT_MESSAGES.
PR_BODYexists only in combination withPR_TITLE. So reaching the ruled setting required the paired change; there was no path toPR_BODYthat left the title alone.What actually changes as a result of that second half:
COMMIT_OR_PR_TITLEused the single commit's subject when a PR had exactly one commit, and the PR title otherwise.PR_TITLEalways uses the PR title. So multi-commit PRs are unaffected, and single-commit PRs now land under their PR title rather than their commit subject. Recording it because it was not in the ruling's words and someone will notice the difference.What this now means in practice
The landed squash message is the PR body — a text an author wrote and a reviewer read.
check:partof-closing-keyword's discharge is reachable for the first time.⚠️ It also makes the PR body load-bearing in a new way: it is no longer just review context, it is the permanent commit message onmain. PR #17454's body is 462 lines, which lands verbatim. That is not wrong — it is an authored text, which is the whole point — but it is worth knowing that body length is now commit-history length.Closing: the decision is taken and applied. ⛔ Option 2 (retire or re-scope the gate) is moot now that the discharge works.
- added a commit that references this issue
on Sep 17, 2026
check:partof-closing-keywordprescribes a discharge that this repository's configuration makes unreachable. Found while landing PR #17454; it is not that PR's problem and not fixable inside it.Measured
Every merge to
maingoes through the queue, squashed, and the queue composes the commit message from the repo setting — there is no per-merge override. So the landed message is the concatenation of the branch's commit messages: in the gate's own words, "a text no one writes and no one reviews, which carries every trailer its inputs carried and can contradict itself where its parts did not."Why that is the gate's problem specifically
scripts/check-partof-closing-keyword.mjsrefuses when commits carry card trailers, and for a branch that is already pushed it states plainly:That discharge requires
squash_merge_commit_message = PR_BODY. WithCOMMIT_MESSAGESit cannot happen, so the check is red on a condition nobody can satisfy and no merge can discharge.main's required checks (TypeScript Type Check,Build Core,Test Core,Dogfood Regression Gate), so it blocks nothing. A gate that can neither be satisfied nor block is a gate with no teeth — it reports a real hazard into a channel nothing acts on.The hazard it is reporting, which is real
PR #17454 carries ten commits with
Refs #17440trailers. UnderCOMMIT_MESSAGESall ten land concatenated onmain. GitHub reads trailers in that landed message, so a card relation declared once in a PR body arrives ten times in a text nobody authored. The gate's header cites a measured precedent — the squash of PR 16247,fc3fb7c4619— where exactly this closed a card from inside a bullet no one read as a declaration.The decision
This is a repository setting, so it is the maintainer's. Two coherent positions:
squash_merge_commit_messagetoPR_BODY. The gate's discharge becomes reachable, the landed message is a text someone wrote and reviewed, and the trailers in individual commits stop mattering.⛔ What should not happen is the current state persisting by default: a gate describing a real hazard, prescribing a remedy the configuration forbids, and gating nothing.
No code change is proposed here. Filing so the choice is recorded rather than rediscovered by the next person who reads that red.
Maintainer-action: set the repository's default squash-merge commit message to the pull request body (GitHub → Settings → General → Pull Requests → “Default commit message” → Pull request title and description) on both
objectstack-ai/objectstackandobjectstack-ai/objectui— done whenGET /repos/objectstack-ai/objectstackandGET /repos/objectstack-ai/objectuiboth reportsquash_merge_commit_message: PR_BODY(read 2026-09-12T00:15Z: bothCOMMIT_MESSAGES).Generated by Claude Code