Skip to content

docs(agents): criterion ③'s test is scoped to the branch's own commits — the unranged git log walked all of main and made force-with-lease dead letter - #18351

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-18178-force-with-lease-criterion-scope
Sep 16, 2026
Merged

hotlong merged 2 commits into
mainfrom
claude/issue-18178-force-with-lease-criterion-scope

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #18178

The defect

AGENTS.md Multi-agent §3 allows --force-with-lease only while all five criteria hold.
Criterion ③ stated the bar — nobody else has ever pushed it — and then operationalised it
as the author and committer sets of an unranged git log over the branch's remote ref.
With no range that walks the branch's entire ancestry, all of main included, so the sets
are main's whole history plus the agent and the test cannot pass for any branch cut from
main. The stated criterion is satisfiable and right; only its operationalisation was
impossible — a reader who follows the words never force-pushes, one who follows the intent
does, and that divergence is the bug. The card was filed after the dev on #17330 declined to
take a reading it could not prove and shipped four wip: commits plus a merge instead.

The change

One hunk, two lines, AGENTS.md :473–:474. The parenthetical is re-keyed to the branch's
own commits — the range form origin/main.. in front of the branch ref. Criteria ①②④⑤
are untouched, ③'s bar is untouched (same words, same strictness), and the file stays at its
1075-line ceiling with no new line.

-   `claude/issue-*`; ② this worktree created it; ③ nobody else has ever pushed it (the
-   author and committer sets of `git log origin/BRANCH` are you alone); ④ no open PR
+   `claude/issue-*`; ② this worktree created it; ③ nobody else has ever pushed it (the author and
+   committer sets of its own commits, `git log origin/main..origin/BRANCH`, are you alone); ④ no open PR

⚠️ BRANCH above stands for the angle-bracket placeholder the file actually spells; this
body writes the word instead, because a GitHub body sanitizer eats short angle-bracket
fragments. The file itself is unchanged in that respect — read the diff for the real bytes.

Ratchet and byte budget

reading before after
wc -l AGENTS.md (ceiling 1075) 1075 1075
line :473 90 B 101 B
line :474 88 B 108 B
lines :472 / :475 / :476 / :477 untouched untouched
git diff --stat 1 file, 2 insertions, 2 deletions

scanLineLengths (the gate's own scanner, budget 120 B) reports 0 offenders on the
edited file.

Reverse verification

Taken on this very branch, a real claude/issue-* branch, and on a constructed control.
⚠️ This checkout is shallow, so the unranged sets are truncated relative to the card's
full-history reading (33 / 32 / 1); the shape is what reproduces, and it reproduces exactly.

BEFORE — today's literal test, on this branch
  git log --format='%an|%cn' origin/claude/issue-18178-force-with-lease-criterion-scope | sort -u
  -> 7 pairs   (Claude|Claude, Jack Zhuang|GitHub, Leehom|GitHub, Warren Buffett|GitHub,
                claude[bot]|GitHub, os-elon-musk|GitHub, os-try-charles|GitHub)

CONTROL — the same literal test on origin/main itself
  -> 6 pairs   — the branch's 7 minus Claude|Claude, i.e. main's set plus the agent, exactly
                 as the card measured. `comm -23` of the two sets prints one line: Claude|Claude.

AFTER — the re-keyed test, same branch
  git log --format='%an|%cn' origin/main..origin/claude/issue-18178-... | sort -u
  -> 1 pair    Claude|Claude          (2 commits in range)

FIRING CONTROL — the re-keyed test on a branch that genuinely has a second pusher
  (a throwaway local repo, two clones, two identities; no probe branch was pushed to origin)
  unshared branch, literal test   -> 4 pairs   (wrongly refuses)
  unshared branch, re-keyed test  -> 1 pair    (permits — correct)
  SHARED branch,   re-keyed test  -> 2 pairs   Claude|Claude + os-try-charles|os-try-charles
                                               (still refuses — the bar is not loosened)

The one design choice: which scoped spelling

The card proposed $(git merge-base origin/main origin/BRANCH)..origin/BRANCH and called it a
proposal, not a prescription. This PR ships the plain two-dot range origin/main..origin/BRANCH.
Both compute the same commit set — verified on the live branch and on both control branches,
1 / 1 / 2 pairs either way — and the two-dot form is one moving part fewer. The deciding
measurement is how each fails:

# the substitution form, when merge-base returns nothing (shallow clone, unrelated
# histories, a ref not fetched) — the range silently becomes HEAD..origin/BRANCH
  exit=0   pairs=0   on a branch a second agent had just pushed  -> "you alone" is
                     VACUOUSLY true, and the criterion permits exactly the clobber it forbids

# the two-dot form under the same fault
  exit=128 fatal: ambiguous argument ... unknown revision  -> loud, no verdict at all

That failure is live in this repository, not hypothetical: git merge-base origin/main REF
exits 1 with empty output here for a branch whose base lies outside the shallow window.
Preferring the spelling that cannot degrade into a different question is the same principle
the card is about — a test that answers confidently and wrongly is worse than one that stops.

维护者速读(草稿)

改了什么AGENTS.md 多 agent 纪律 §3 里判断「分支是不是只有我一个人推过」的那条命令,
git log 加分支名(走遍整条祖先,包含整个 main),改成只看分支自己的提交(origin/main.. 区间)。
正文两行,判据本身一字未动。

为什么改 — 原命令对任何从 main 切出来的分支都必然返回「多人」,所以第五条允许的
--force-with-lease 实际上永远用不了:条文写着允许,测出来永远禁止。按字面读的 agent 从不 force-push,
按意图读的会,两种读法分叉本身就是缺陷。已经有一张卡的开发因此多推了四个 wip: 提交和一个 merge。

风险与代价(含回滚) — 改的是规则文本,不是代码,没有运行时影响。风险是「改松了判据」,
本 PR 用一个构造出来的、确有第二个推送者的分支做了发火对照:新命令仍然读出两组身份、仍然拒绝。
回滚成本 = 还原两行文字。

席位意见 — (留空,待席位定稿)

你要做的 — 这是受管面(AGENTS.md),需要您的一次授权批准;批准前没有任何 agent 席位会合并、
入队或挂 auto-merge,PR 保持 draft。

Acceptance notes

  • noted, not filed: the ratchet's line-length verdict prints only on failure
    (check-skill-line-ratchet.mjs run() logs lv.msg under if (!lv.ok)), so a green run
    says nothing about the 120-byte axis for any file. Not a defect class — a silent green is
    the normal shape for that gate — measured positively here by calling scanLineLengths
    directly instead of reading the gate's silence. Bearer: none; noted for the next author who
    cites that gate's green as width evidence.
  • Scope held: AGENTS.md only, criterion ③'s parenthetical only; :472 and :475–:477 were not
    re-flowed and not touched.

Landing

Governed surface (AGENTS.md, Prime Directive #14) — check-governed-merges.mjs --test
answers GOVERNED, 1 of 1 path. The PR stays draft; no reviewer was requested, no
auto-merge armed, nothing flipped. It waits for the maintainer's authorized approval, which
the owning seat requests.


Generated by Claude Code

Multi-agent §3 lets a branch be force-pushed with --force-with-lease only
while all five criteria hold. Criterion ③ states the bar — nobody else has
ever pushed it — and then operationalised it as the author and committer
sets of an UNRANGED `git log origin/<branch>`, which walks the branch's
entire ancestry, all of `main` included. So the sets are main's whole
history plus the agent and the test can never pass for a branch cut from
`main`: a reader who follows the words never force-pushes, one who follows
the intent does.

Re-key the parenthetical to the branch's OWN commits,
`git log origin/main..origin/<branch>`. The bar is untouched and criteria
①②④⑤ are untouched; the double-dot range is preferred over a
`$(git merge-base …)..` substitution because an empty substitution
degrades silently into `HEAD..origin/<branch>` at exit 0 — measured on a
constructed control, that reads zero pairs on a branch a second agent had
just pushed, i.e. it permits exactly the clobber the criterion forbids.

Claude-Session: https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xs documentation Improvements or additions to documentation labels Sep 16, 2026
@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 16, 2026 — with Claude

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ec9ef17b06d0a9bf4255b6f066c749c6f02c1b27

Reviewing seat: domain:skills, in-seat review at the contract-review tier — get_session read at 2026-09-15T07:15Z: configured_model = session_context.model = last_served_model, the constant's tier, no fallback notice this round. Card #18178, claim 5690957832, report 5691115691 (posted through the tool; read back clean, author os-zhuang — the proxy's write identity since 2026-09-16T01:53Z, #18350); Clause-②: no on the claim; default-tier build, seat review at tier.

① Derived judgments

  • Accept set / public surface: none moves — AGENTS.md :473–:474 (2 insertions / 2 deletions, the only file) against merge base ceb6b5fb (= origin/main; the dev merged it in when the derivation flagged a stale tree, never rebased); nothing published (70 manifests scanned, 0 ship AGENTS.md); skip-changeset read back (documentation + size/xs + skip-changeset). GOVERNED (AGENTS.md by check-governed-merges --test, exit 3) ⇒ the four-piece hangs here and the PR waits for an authorized APPROVED; ruling C after it.
  • The change, read whole on a detached worktree of the head: criterion ③'s parenthetical now reads 「(the author and committer sets of its own commits, git log origin/main..origin/<branch>, are you alone)」 — the two-dot range is the branch's own commits, the bar 「nobody else has ever pushed it」 unchanged, criteria ①②④⑤ untouched; :473 90 → 101 B, :474 88 → 108 B, neighbours unchanged, 1075 / 1075 lines. Seat re-runs on the head: check:pm-skill-ratchet 0 (1075 / 1075; the widest table row pin untouched), check:pm-skill-id-lint 0 (no issue number in the text), check:skill-frame-sync 0, check:pm-governed-prose 0, scanLineLengths 0 offenders, --pair 18351 0. The seat's own probe on the shared checkout (140-commit window): this PR's branch reads 11 author|committer pairs unranged and exactly ONE (Claude|Claude) under the two-dot range; main unranged reads 10 — the set relation the card states (branch set = main's set + the agent) holds at this window as at the card's full-history one (33 / 32 / 1).
  • Gates 14 / 14 / 0 / 0 by --ran on the head (plus skill-frame-sync and the governed path face recorded outside the derivation — the latter's exit 3 IS the GOVERNED verdict, not NOT-MEASURED). Reverse verification: the literal test at two windows (7 / 6 and 11 / 10 pairs, branch / main) vs the scoped test (1 at both); the FIRING CONTROL on a constructed repo — an unshared branch the literal test refused (4 pairs) is permitted by the re-keyed test (1), and a branch a second identity really pushed is STILL refused (2 pairs); git diff --stat origin/main...HEAD = the two lines only.

② Semver level

Not applicable — nothing published moves; skip-changeset measured.

③ Boundary flags

open_questions: none. One design choice met and argued on the four axes — the plain two-dot range (shipped) over the card's $(git merge-base …).. spelling: both compute the same set (1 / 1 / 2 on the live branch and both controls), but on a shallow clone git merge-base can exit 1 with EMPTY output, and the substitution then collapses to ..origin/<branch> = HEAD..origin/<branch>, exit 0, reporting ZERO pairs for a branch a second agent had just pushed — a silent permission where the old text over-refused; the two-dot form under the same fault exits 128. The seat CONFIRMS the choice: the defect was a test that answers confidently and wrongly, and its replacement must not have a failure mode that grants a force-push. Two seat premises refuted and owned: the card's branch claude/issue-17330-ambient-roots-app is gone from origin (re-measured on this PR's branch and on another live one); the dispatch did not name the checkout SHALLOW (the unranged counts are window-bounded; the set relation reproduces regardless — and the shallowness became the deciding evidence). Five deviations answered: the spelling — accepted, argued; the 17-byte gloss 「its own commits」 inside the parenthetical — accepted (the intent survives a future mangling of the command); origin/main merged in — accepted; the PR body quotes the 50-commit window's 7 / 6 / 1 while the report carries the 140-commit 11 / 10 / 1 — accepted, both declared shallow, no PATCH owed; the dev's probing fetched with --depth and moved the SHARED .git/shallow window (98 → 62 → 140 commits, restored deeper than found; probe refs deleted) — accepted as a repair with a note: the shared object store is common to every worktree, and this seat's check-harness-current still reads CURRENT on it. Out of scope, noted not filed: check-skill-line-ratchet.mjs prints its width verdict only on failure (a green is silent on that axis; the seat measured it directly); 261 claude/issue-* branches remain on origin. Landing: governed — the four-piece hangs here (this record, the 速读终稿 below, the ACCEPT on #18178, needs-user-decision, reviewers os-zhuang + hotlong requested); ⛔ this seat writes no approving review; ruling C after an authorized APPROVED.

Implemented-by: claude/issue-18178-force-with-lease-criterion-scope
Reviewed-by: session_01HZfg2AwVX191qCizp88gQr

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)— PR #18351 / #18178

改了什么:AGENTS.md §3「不得强推共享分支」的五条判据里,③「没有别人推过这条分支」的括号检验写的是无范围的 git log origin/<branch>,它会走完整个 main 的祖先,所以任何从 main 切出的分支都通不过(实测:分支 33 对作者|提交者,main 本身 32,分支自己的提交只有 1 对)。结果是这条允许 --force-with-lease 的规则成了死信:#17330 的 dev 宁可推四个 wip: 提交加一次合并,也不敢做一个自己证明不了的读数。改法:括号改为「its own commits, git log origin/main..origin/<branch>」,只量分支自己的提交;判据③的门槛本身与其余四条一字未动;两行原地改,1075/1075 行不变,每行 ≤120 字节。

验证:本席在共享检出上复测:本 PR 分支无范围 11 对、两点范围恰 1 对(Claude|Claude),main 无范围 10 对 —— 卡上的集合关系(分支集 = main 集 + 代理)在浅克隆窗口下同样成立;dev 用构造仓做了发火对照:真有第二个推送者的分支在新检验下仍读 2 对、仍拒绝,门槛没有放松。ratchet / id-lint / frame-sync / governed-prose 绿,--pair 0,14 条派生门禁全绿。

一个设计选择,本席确认:dev 没用卡上建议的 $(git merge-base …).. 写法而用了两点范围。理由实测:浅克隆上 git merge-base 可能空输出退出 1,替换后命令退化成 HEAD..origin/<branch>、exit 0、对刚被第二个人推过的分支读出 0 对 —— 静默放行,比原来的过度拒绝更糟;两点写法在同样故障下 exit 128 响亮失败。

要你做的:审 PR #18351,同意即 APPROVE;之后由本席按裁定 C 落地。

顺带:dev 探测时用 --depth 拉取动了共享对象库的浅克隆窗口(98 → 62 → 140,已加深还原),探测 ref 已清;本席 harness 读数仍 CURRENT。PR 作者与报告作者已读作 os-zhuang(身份变化见 #18350)。


Generated by Claude Code

@os-zhuang
os-zhuang requested a review from hotlong September 16, 2026 02:32
@hotlong
hotlong marked this pull request as ready for review September 16, 2026 06:36
@hotlong
hotlong enabled auto-merge September 16, 2026 06:36
@hotlong
hotlong added this pull request to the merge queue Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Ruling-C landing provenance — skills seat (signed off, 留守 on this PR), session session_01HZfg2AwVX191qCizp88gQr, 2026-09-16T06:38Z. Governed rules layer (AGENTS.md :473–:474, criterion ③'s parenthetical scoped to the branch's own commits): the authorized APPROVED review is hotlong's 5219265450 at 2026-09-16T06:35Z on the current head ec9ef17b06d0a9bf4255b6f066c749c6f02c1b27 (not dismissed) — the record 5691146373 PASS is on that same head, check-clause2-carriers.mjs --pair 18351 exits 0 (C6-RECORD names the record on this head), every check on the head reads green (34 check-runs: 18 success · 16 skipped, every skip in the roster — check-expected-skips.mjs --head ec9ef17b exit 0), mergeable_state: clean before the flip. The seat cleared needs-user-decision at 2026-09-16T06:36Z (read back: documentation + size/xs + skip-changeset). The approver pre-empted the landing strokes: ready_for_review by hotlong at 2026-09-16T06:36Z, auto_merge_enabled by hotlong at 2026-09-16T06:36Z (the stored method reads merge, as the register says it always does; the queue rule enforces SQUASH), added_to_merge_queue at 2026-09-16T06:37Z; the queue ref refs/heads/gh-readonly-queue/main/pr-18351-* read 1 at 2026-09-16T06:37Z. The seat's own POST …/ccr/ready_for_review (twice) and PUT …/ccr/auto_merge (four times) answered HTTP 503 「GitHub is temporarily unavailable. Retry shortly.」 in the same minute — a platform outage on GitHub's side, not the proxy's, superseded by the approver's strokes; no seat write landed on the PR besides the label. ⛔ No approving review was written by this seat; the queue is the only landing path; merged_at and the single-parent squash are the landing readings, posted on #18178.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants