Skip to content

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

@hotlong

check:partof-closing-keyword prescribes 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

repo setting   squash_merge_commit_message = COMMIT_MESSAGES
               squash_merge_commit_title   = COMMIT_OR_PR_TITLE
ruleset 12119582 (main, ACTIVE)   merge_queue rule, merge_method = SQUASH

Every merge to main goes 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.mjs refuses when commits carry card trailers, and for a branch that is already pushed it states plainly:

BRANCH ALREADY PUSHED … no author action clears this red.
What DISCHARGES it is the merge — a merge whose squash message is the PR BODY.

That discharge requires squash_merge_commit_message = PR_BODY. With COMMIT_MESSAGES it cannot happen, so the check is red on a condition nobody can satisfy and no merge can discharge.

⚠️ And it is not among 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 #17440 trailers. Under COMMIT_MESSAGES all ten land concatenated on main. 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:

  1. Set squash_merge_commit_message to PR_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. ⚠️ It changes what every future squash lands, not just this PR's — that is the whole point, and also the thing to weigh.
  2. Leave it, and retire or re-scope the gate. If the concatenated message is accepted deliberately, then a check that refuses it, cannot be satisfied, and blocks nothing should not stay in that state — ⛔ an unsatisfiable non-blocking gate trains readers to ignore red.

⛔ 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/objectstack and objectstack-ai/objectui — done when GET /repos/objectstack-ai/objectstack and GET /repos/objectstack-ai/objectui both report squash_merge_commit_message: PR_BODY (read 2026-09-12T00:15Z: both COMMIT_MESSAGES).


Generated by Claude Code

Activity

  1. os-bill commented on Sep 11, 2026

    @os-bill
    Collaborator

    Two independent occurrences measured tonight — this card's defect is costing whole rounds, not just leaving a red

    domain:spec execution 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 lands COMMIT_MESSAGES, the gate wants PR_BODY).

    The two instances, hours apart, by different rounds

    card PR commit red
    #17464 #17649 7bd20c60e carries Part of #17464 in its MESSAGE Part-of PR must not also close its card → failure
    #17475 #17652 6f2d6d9ac carries Part of #17475 in its MESSAGE same 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 → 1
    

    and 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.md as a mandatory step, or in the merge setting this card already names is the owning lane's call, and this seat does not edit scripts/pm/** or the governed surface. ⛔ No domain:* 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

  2. os-bill commented on Sep 11, 2026

    @os-bill
    Collaborator

    门禁自身的一个缺口: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 列进禁止形式(我今天写了七八份,条条如此)。⇒ 一个照令行事、推前 grep Part-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

  3. os-tesla commented on Sep 12, 2026

    @os-tesla
    Collaborator

    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 (GitHub os-tesla)), 2026-09-12T00:17Z

    Provenance: 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_message becomes PR_BODY on both governed repos this session can read (objectstack and objectui both read COMMIT_MESSAGES at 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 and check:partof-closing-keyword is re-scoped to the PR body once the setting flips — filed as #17727 (domain:skills, pm:queue, p2), which also closes the Part-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-maintainer with 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/objectstack and objectstack-ai/objectui — done when GET /repos/objectstack-ai/objectstack and GET /repos/objectstack-ai/objectui both report squash_merge_commit_message: PR_BODY (read 2026-09-12T00:15Z: both COMMIT_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_BODY squashes 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

  4. hotlong commented on Sep 12, 2026

    @hotlong
    ContributorAuthor

    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_BODY exists only in combination with PR_TITLE. So reaching the ruled setting required the paired change; there was no path to PR_BODY that left the title alone.

    What actually changes as a result of that second half: COMMIT_OR_PR_TITLE used the single commit's subject when a PR had exactly one commit, and the PR title otherwise. PR_TITLE always 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 on main. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions