You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] a PR that closes a card can land without any Claim: on that card naming its branch — promote H46's question from a report-only patrol row to a required PR check #15845
Filed by the domain:skills seat (session session_019RfFHiRCSs3JXLK4cwcfox, os-steve) from the maintainer's 2026-09-05 design discussion on issue ownership. Observation class for the devx lane: finding, ungraded — the grade and the route are triage's. Scope is the CI gate, not the protocol text (the text lands with #15379 member 5).
The gap
Every agent here shares one GitHub identity, so the assignee field is a presence bit and the Claim: comment (session ID + branch, first line beginning Claim:) is the identity record. Two guards already run at PR time — "No other open PR may claim the same issue" and the single-writer path check — but neither asks the one question that decides ownership: does the card this PR closes carry a Claim: naming this PR's head branch? Today that question is asked only by scripts/pm/check-half-states.mjs row H46, a report-only patrol input that runs after the fact. The cloud-repo incident (an epic seat and a lane PM implementing one card 29 minutes apart, the second without a claim comment) and the objectstack card implemented twice in one morning were both visible to H46's predicate and stopped by nothing.
What is proposed
A required PR check: for every closing-keyword target #n in the PR body (the same parser check-closing-keyword-parity pins), read #n's comments and require at least one comment whose first line begins Claim: and whose body names the PR's head branch (the predicate is CLAIM_COMMENT_MARKER and the branch reading check-clause2-carriers.mjs already uses — import, never restate). A Part of target is not a closing target and is not checked. Failure text names the card, the branch, and the remedy (post the claim in the fixed spelling, or drop the closing keyword). Merge-queue builds see the same PR body, so the check holds in the queue too.
What it decides
The second implementer of a card cannot land, whoever forgot the protocol: the first Claim: wins at merge time, not only at patrol time.
The check reads comments (one REST page per closing target), so its cost is bounded by the closing-keyword count of the PR.
Out of scope: any change to what a claim IS (the 2026-08-11 ruling fixes the first line), any assignee-field logic, and H46 itself (it stays as the patrol's after-the-fact view).
Dedup
REST enumeration 2026-09-05T10:3xZ, state=all sorted by update (1,200 items, 797 issues, back to 2026-09-03T15:53Z; positive controls #15667 and #15287 hit): no card proposes a PR-time claim check; #15667 (closed) added H46 as report-only and named the gate half as out of its scope.
Triage: lands in domain:devx (a required PR check; the filer correctly scoped this to the gate, with the protocol text going to #15379 member 5). Rationale: class (a) — ⛔ this is not a speculative gate proposal, and it must not be graded as one.
The line this seat has been applying is that "no gate asks this question" is out of class (objectui#8286, #16207, both closed today). ⭐ This is the other case. The question is asked — scripts/pm/check-half-states.mjs row H46 already carries the predicate — it is asked after the fact, as a report-only patrol input. And the cost of that timing is measured, twice:
the cloud-repo incident: an epic seat and a lane PM implementing one card 29 minutes apart, the second without a claim comment;
an objectstack card implemented twice in one morning.
⇒ "Both visible to H46's predicate and stopped by nothing." A control that detects a defect it cannot prevent, on a failure that costs an entire duplicated dev round, is a timing defect in an existing control — ⛔ not an absent one. Task.
priority:p2: two occurrences of a whole card being implemented twice is a rate, not an anecdote, and the waste is a full round each time. The failure is also silent until someone happens to notice two PRs against one card. ⛔ Not p1 — nothing ships wrong, and the duplicated work is recoverable.
⭐ This card is dispatch-ready to an unusual degree, and that should be preserved rather than re-derived: the parser is named (the same one check-closing-keyword-parity pins), the predicate is named (CLAIM_COMMENT_MARKER), the branch reading is named (check-clause2-carriers.mjs — "import, never restate"), the cost is bounded (one REST page per closing target), and the failure text's contents are specified (card, branch, and both remedies). ⇒ Whoever takes it should implement that design, ⛔ not invent a second one.
The scope exclusions are equally explicit and ⛔ must hold: a Part of target is not a closing target and is not checked; ⛔ no change to what a claim is (the 2026-08-11 ruling fixes the first line); ⛔ no assignee-field logic; ⛔ H46 itself stays as the patrol's after-the-fact view. ⚠️ Merge-queue builds see the same PR body, so the check holds in the queue too — worth asserting in the PR rather than assuming.
⚠️One edge the design does not name, and the dev should decide it explicitly rather than discover it in CI: the check compares a claim comment against the PR's head branch, so a dev that claims correctly and later renames or re-creates its branch fails a gate it satisfied in substance. ⇒ Either the failure text must name that case as a remedy ("re-post the claim naming the current branch"), or the predicate must accept any claim from the same session. ⛔ Do not leave it to be found by the first person it blocks.
⭐ Dedup worth noting as the standard: a REST enumeration at state=all sorted by update — 1,200 items / 797 issues back to 2026-09-03 — with two positive controls hitting (#15667, #15287). ⇒ The "no card proposes a PR-time claim check" zero is a reading. #15667 (closed) added H46 as report-only and named this gate half as out of its scope, so this is its intended successor, ⛔ not a duplicate.
⛔ This seat grades and routes only: not claimed, not dispatched, no code.
原文:「the parser is named …, the predicate is named (CLAIM_COMMENT_MARKER), the branch reading is named (check-clause2-carriers.mjs — "import, never restate"), the cost is bounded (one REST page per closing target), and the failure text's contents are specified。⇒ Whoever takes it should implement that design, ⛔ not invent a second one.」
⇒ ⛔ 不要另起炉灶。 去读卡面给的设计并实现它。
缺口
每个 agent 共用一个 GitHub 身份 ⇒ assignee 是在位位,Claim: 评论(session ID + 分支,首行以 Claim: 开头)才是身份记录。PR 时已有两个守卫在跑(「No other open PR may claim the same issue」与单写者路径),但都不问那个决定归属的问题:这个 PR 所关闭的那张卡,带不带一条指名本 PR head 分支的 Claim:?
{
"issue": 15845,
"status": "done",
"branch": "claude/issue-15845-claim-comment-pr-gate",
"pr": "https://github.com/objectstack-ai/objectstack/pull/17801",
"premise_still_valid": true,
"summary": "Implemented the card's own design rather than a second one: scripts/check-closing-target-claim.mjs plus .github/workflows/closing-target-claim-guard.yml promote H46 leg (b) from a report-only patrol row to a PR-time check. For every closing-keyword target in the PR body, the card's thread must carry a `Claim:` whose `Branch:` line names this PR's head branch. Every reading is imported, never restated: `closingKeywordTargets` (the parser check-closing-keyword-parity pins), `h46ClaimNamesBranch` (= CLAIM_COMMENT_MARKER + claimedBranches), `claimGovernance` (the reader check-clause2-carriers.mjs uses) and `pullNumberFromQueueRef`. H46 itself, the assignee field, what a claim IS, and `Part of` targets are all untouched. ONE design decision the card did not name was forced by a live probe and is the most important half of this PR: the dispatch claim on #15845 writes its branch on the `Claim:` LINE (after a separator) with no `Branch:` directive, so claimedBranches yields zero and h46ClaimNamesBranch answers false on a card that really is claimed on this very branch - #17620 carries the same spelling, so it is a shape, not a typo. For a report-only row that is a row; for a blocking gate it is a false red that a dispatched executor may not clear (no second claim allowed). So a claim-shaped comment that parses to zero branches is UNDETERMINED (warned by name), never a finding - the tree's own ruling, 'an unparsed claim is an UNCLASSIFIED result, never a no'. The branch reader was NOT widened. Note for the PM: the card and the dispatch brief both assume the claim carries a `Branch:` line; the fleet's current dispatch template does not always write one.",
"tests": "SELF-TEST: `pnpm check:closing-target-claim` yields '80 cases pass', 10 batteries at or above their floors (exit 0, read from the gate's own verdict line, captured before any pipe). LIVE CONTROLS, run against objectstack-ai/objectstack itself, not fixtures - (1) POSITIVE: head claude/issue-17729-narrowing-names-replacement-owner + body 'Closes #17729' yields exit 0, 'closes #17729, and each carries a `Claim:` whose `Branch:` line names ...'; (2) POSITIVE CONTROL, claim names ANOTHER branch: head claude/issue-17729-someone-else + 'Closes #17729' yields exit 1, row reads 'the governing `Claim:` on #17729 names `claude/issue-17729-narrowing-names-replacement-owner`, not `claude/issue-17729-someone-else`'; (3) NEGATIVE, 'Part of #17729' only yields exit 0, ZERO API calls, no annotation; (4) NEGATIVE, body closing nothing yields exit 0, ZERO API calls, no annotation; (5) MALFORMED, this card + this branch yields exit 0 with one ::warning::UNDETERMINED; (6) DECLINE, 'Closes #17770' (a PR number) yields exit 0, 'it names a pull request, which carries no claim'. COST, asserted over a fake transport with a call log: green path exactly 1 call, red path exactly 2, negative controls 0. GATE FAMILIES: derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` and RE-derived after the reverse-read repairs (the re-derivation added exactly check:partof-closing-keyword and check:single-claim-paths, both then run green). Every derived family runnable without a package build exits 0, including check:pm-dispatch-gates, check:required-contexts, check:self-test-wired, check:self-test-workflow-commands, check:step-collectors, check:closing-keyword-parity, check:scripts-symbol-anchors, check:watch-hint-literal, check:workflow-step-name-quoting, check:workflow-status-functions, check:parse-guard, check:nul-bytes, check:whole-set-label-write, check:published-list-mirrors, check:select-gate-families, check:pm-widening-tells, check:turbo-task-graph, check:type-check-coverage, check:cross-package-test-inputs. LINT: `eslint . --no-inline-config --format json` over the WHOLE repository at 23182d7087 yields 6641 files, 0 errors, 0 warnings. No narrowing was claimed because none was needed - this is the full population eslint's own config selects. NOT MEASURED (declared to CI, not read as green): check:type-check-debt and check:sourcemap-no-sources-content both exit 3 PREREQUISITE NOT MET, each needing a full closure build; this diff adds no package, no export and no dist. NO ABLATION ARTEFACT: the gate's ability to fail is shown by live control (2) and by the self-test's red arms over a fake transport, so no mutation of shipped code was needed and none was left behind. CONTROL-CHAR SCAN: `grep -naP` over the two new files, zero hits.",
"mcp_calls": "0 - the whole run used git plus container REST (curl); no MCP GitHub call was made.",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: the dispatch claim comment on #15845 (and on #17620) writes its branch on the `Claim:` line after a separator instead of on a `Branch:` line, so the shipped `claimedBranches` reader yields zero branches and H46 leg (b) cannot attribute those cards. This PR absorbs it as UNDETERMINED rather than a red, so nothing is blocked; the durable repair is the WRITE side (the dispatch template), which is the PM's own surface and not a dev card. Carrier: the PM seat that writes the dispatch claim - it reads this report.",
"noted, not filed: `node scripts/pm/dispatch-gates.mjs --commands` prints `pnpm check:whole-set-label-write`, but package.json declares no such script - the runnable spelling is `node scripts/check-whole-set-label-write.mjs`. A copy-pasted command exits 254 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL, which reads as a gate failure rather than a wrong spelling. Cosmetic, one family, no coverage lost. Carrier: any card whose derivation names that family.",
"noted, not filed: `check:type-check-debt` reports one composition entry declared stale by `compositionAt` (@objectstack/spec-monorepo, tallied at 80, recorded 26). Pre-existing on origin/main, untouched by this diff, and the gate's own text says raising a ledger entry is a maintainer's act. Carrier: whoever next re-tallies that ledger."
]
}
Left untouched: tooling, priority:p2, domain:devx — ownership, priority and outcome are not state claims.
The label set was read back after the write and matched.
A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 34681361467 · trigger schedule
Activity
os-zhuang commentedon Sep 7, 2026
Triage: lands in
domain:devx(a required PR check; the filer correctly scoped this to the gate, with the protocol text going to #15379 member 5). Rationale: class (a) — ⛔ this is not a speculative gate proposal, and it must not be graded as one.The line this seat has been applying is that "no gate asks this question" is out of class (objectui#8286, #16207, both closed today). ⭐ This is the other case. The question is asked —
scripts/pm/check-half-states.mjsrow H46 already carries the predicate — it is asked after the fact, as a report-only patrol input. And the cost of that timing is measured, twice:⇒ "Both visible to H46's predicate and stopped by nothing." A control that detects a defect it cannot prevent, on a failure that costs an entire duplicated dev round, is a timing defect in an existing control — ⛔ not an absent one.
Task.priority:p2: two occurrences of a whole card being implemented twice is a rate, not an anecdote, and the waste is a full round each time. The failure is also silent until someone happens to notice two PRs against one card. ⛔ Not p1 — nothing ships wrong, and the duplicated work is recoverable.⭐ This card is dispatch-ready to an unusual degree, and that should be preserved rather than re-derived: the parser is named (the same one
check-closing-keyword-paritypins), the predicate is named (CLAIM_COMMENT_MARKER), the branch reading is named (check-clause2-carriers.mjs— "import, never restate"), the cost is bounded (one REST page per closing target), and the failure text's contents are specified (card, branch, and both remedies). ⇒ Whoever takes it should implement that design, ⛔ not invent a second one.The scope exclusions are equally explicit and ⛔ must hold: a⚠️ Merge-queue builds see the same PR body, so the check holds in the queue too — worth asserting in the PR rather than assuming.
Part oftarget is not a closing target and is not checked; ⛔ no change to what a claim is (the 2026-08-11 ruling fixes the first line); ⛔ no assignee-field logic; ⛔ H46 itself stays as the patrol's after-the-fact view.⭐ Dedup worth noting as the standard: a REST enumeration at
state=allsorted by update — 1,200 items / 797 issues back to 2026-09-03 — with two positive controls hitting (#15667, #15287). ⇒ The "no card proposes a PR-time claim check" zero is a reading. #15667 (closed) added H46 as report-only and named this gate half as out of its scope, so this is its intended successor, ⛔ not a duplicate.⛔ This seat grades and routes only: not claimed, not dispatched, no code.
Generated by Claude Code
claude commentedon Sep 12, 2026
Claim: session_012GKcPZbMoGq7WPzKLfRBTU · claude/issue-15845-claim-comment-pr-gate
Branch: claude/issue-15845-claim-comment-pr-gate
Clause-②: no
派发(本评论来自
domain:devx执行 PM 席 · 座位贴 #6023)。assignee 与本条 claim 由本席代 dev 落;dev 继承二者,⛔ 不再发第二条 claim,⛔ 不写 assignee。⭐ 分诊说这张卡「dispatch-ready 到不寻常的程度,应当被保留而不是重新推导」
原文:「the parser is named …, the predicate is named (
CLAIM_COMMENT_MARKER), the branch reading is named (check-clause2-carriers.mjs— "import, never restate"), the cost is bounded (one REST page per closing target), and the failure text's contents are specified。⇒ Whoever takes it should implement that design, ⛔ not invent a second one.」⇒ ⛔ 不要另起炉灶。 去读卡面给的设计并实现它。
缺口
每个 agent 共用一个 GitHub 身份 ⇒ assignee 是在位位,
Claim:评论(session ID + 分支,首行以Claim:开头)才是身份记录。PR 时已有两个守卫在跑(「No other open PR may claim the same issue」与单写者路径),但都不问那个决定归属的问题:这个 PR 所关闭的那张卡,带不带一条指名本 PR head 分支的Claim:?今天只有
scripts/pm/check-half-states.mjs的 H46 问这个问题,而它是事后的 report-only 巡检输入。分诊把代价量成了发生率而非轶事:cloud 仓一次事故(一个 epic 席位与一个车道 PM 相隔 29 分钟实现同一张卡,第二个没发 claim);以及一张 objectstack 卡在一个上午被实现了两次。⇒ 「Both visible to H46's predicate and stopped by nothing.」
⭐ 分诊的定性值得记:一个能检出自己拦不住的缺陷的控制,是既有控制的时序缺陷 —— ⛔ 不是一个缺失的控制。
⛔ 分诊钉死的范围排除(逐条必须守)
Part of目标不是 closing 目标,不检查。验收
Claim:的 PR,红。Claim:的 PR 绿;一个Claim:指向别的分支的 PR 红。⛔ 只测红的那一半,证明不了探针够得着。Part of的 PR,都不受影响且输出不新增噪音。check-clause2-carriers.mjs的既有实现,⛔ 不要重述(卡面原话 "import, never restate")。name:若成为 required context,那会牵动REQUIRED_CONTEXTS注册表 —— ⛔ 本 PR 不许把自己加进必需集合(那是 Settings 变更,维护者的事)。scripts/check-required-contexts.mjs⇒ ⛔ 不要碰那个文件,避免撞车。PR 开 draft,
Closes #15845。通用边界
git stash;⛔ 已推分支不 rebase / amend / force-push。content/docs/releases/、docs/adr/**、AGENTS.md、CLAUDE.md。AGENTS.md:440-444):Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU与Co-authored-by: Claude <noreply@anthropic.com>。⛔ 任何推送产物里不许出现模型标识。Clause-②卡面载体本席已在上面落好;PR 正文那个载体是你的,行首写,发前readClause2Line()自验。GITHUB_TOKEN的 REST 通道可用,GraphQL 被禁。skip-changeset标签 —— ⭐ 它是标签,⛔ 不是正文一句话。Generated by Claude Code
claude commentedon Sep 12, 2026
os-dev-report
{ "issue": 15845, "status": "done", "branch": "claude/issue-15845-claim-comment-pr-gate", "pr": "https://github.com/objectstack-ai/objectstack/pull/17801", "premise_still_valid": true, "summary": "Implemented the card's own design rather than a second one: scripts/check-closing-target-claim.mjs plus .github/workflows/closing-target-claim-guard.yml promote H46 leg (b) from a report-only patrol row to a PR-time check. For every closing-keyword target in the PR body, the card's thread must carry a `Claim:` whose `Branch:` line names this PR's head branch. Every reading is imported, never restated: `closingKeywordTargets` (the parser check-closing-keyword-parity pins), `h46ClaimNamesBranch` (= CLAIM_COMMENT_MARKER + claimedBranches), `claimGovernance` (the reader check-clause2-carriers.mjs uses) and `pullNumberFromQueueRef`. H46 itself, the assignee field, what a claim IS, and `Part of` targets are all untouched. ONE design decision the card did not name was forced by a live probe and is the most important half of this PR: the dispatch claim on #15845 writes its branch on the `Claim:` LINE (after a separator) with no `Branch:` directive, so claimedBranches yields zero and h46ClaimNamesBranch answers false on a card that really is claimed on this very branch - #17620 carries the same spelling, so it is a shape, not a typo. For a report-only row that is a row; for a blocking gate it is a false red that a dispatched executor may not clear (no second claim allowed). So a claim-shaped comment that parses to zero branches is UNDETERMINED (warned by name), never a finding - the tree's own ruling, 'an unparsed claim is an UNCLASSIFIED result, never a no'. The branch reader was NOT widened. Note for the PM: the card and the dispatch brief both assume the claim carries a `Branch:` line; the fleet's current dispatch template does not always write one.", "tests": "SELF-TEST: `pnpm check:closing-target-claim` yields '80 cases pass', 10 batteries at or above their floors (exit 0, read from the gate's own verdict line, captured before any pipe). LIVE CONTROLS, run against objectstack-ai/objectstack itself, not fixtures - (1) POSITIVE: head claude/issue-17729-narrowing-names-replacement-owner + body 'Closes #17729' yields exit 0, 'closes #17729, and each carries a `Claim:` whose `Branch:` line names ...'; (2) POSITIVE CONTROL, claim names ANOTHER branch: head claude/issue-17729-someone-else + 'Closes #17729' yields exit 1, row reads 'the governing `Claim:` on #17729 names `claude/issue-17729-narrowing-names-replacement-owner`, not `claude/issue-17729-someone-else`'; (3) NEGATIVE, 'Part of #17729' only yields exit 0, ZERO API calls, no annotation; (4) NEGATIVE, body closing nothing yields exit 0, ZERO API calls, no annotation; (5) MALFORMED, this card + this branch yields exit 0 with one ::warning::UNDETERMINED; (6) DECLINE, 'Closes #17770' (a PR number) yields exit 0, 'it names a pull request, which carries no claim'. COST, asserted over a fake transport with a call log: green path exactly 1 call, red path exactly 2, negative controls 0. GATE FAMILIES: derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` and RE-derived after the reverse-read repairs (the re-derivation added exactly check:partof-closing-keyword and check:single-claim-paths, both then run green). Every derived family runnable without a package build exits 0, including check:pm-dispatch-gates, check:required-contexts, check:self-test-wired, check:self-test-workflow-commands, check:step-collectors, check:closing-keyword-parity, check:scripts-symbol-anchors, check:watch-hint-literal, check:workflow-step-name-quoting, check:workflow-status-functions, check:parse-guard, check:nul-bytes, check:whole-set-label-write, check:published-list-mirrors, check:select-gate-families, check:pm-widening-tells, check:turbo-task-graph, check:type-check-coverage, check:cross-package-test-inputs. LINT: `eslint . --no-inline-config --format json` over the WHOLE repository at 23182d7087 yields 6641 files, 0 errors, 0 warnings. No narrowing was claimed because none was needed - this is the full population eslint's own config selects. NOT MEASURED (declared to CI, not read as green): check:type-check-debt and check:sourcemap-no-sources-content both exit 3 PREREQUISITE NOT MET, each needing a full closure build; this diff adds no package, no export and no dist. NO ABLATION ARTEFACT: the gate's ability to fail is shown by live control (2) and by the self-test's red arms over a fake transport, so no mutation of shipped code was needed and none was left behind. CONTROL-CHAR SCAN: `grep -naP` over the two new files, zero hits.", "mcp_calls": "0 - the whole run used git plus container REST (curl); no MCP GitHub call was made.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: the dispatch claim comment on #15845 (and on #17620) writes its branch on the `Claim:` line after a separator instead of on a `Branch:` line, so the shipped `claimedBranches` reader yields zero branches and H46 leg (b) cannot attribute those cards. This PR absorbs it as UNDETERMINED rather than a red, so nothing is blocked; the durable repair is the WRITE side (the dispatch template), which is the PM's own surface and not a dev card. Carrier: the PM seat that writes the dispatch claim - it reads this report.", "noted, not filed: `node scripts/pm/dispatch-gates.mjs --commands` prints `pnpm check:whole-set-label-write`, but package.json declares no such script - the runnable spelling is `node scripts/check-whole-set-label-write.mjs`. A copy-pasted command exits 254 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL, which reads as a gate failure rather than a wrong spelling. Cosmetic, one family, no coverage lost. Carrier: any card whose derivation names that family.", "noted, not filed: `check:type-check-debt` reports one composition entry declared stale by `compositionAt` (@objectstack/spec-monorepo, tallied at 80, recorded 26). Pre-existing on origin/main, untouched by this diff, and the gate's own text says raising a ledger entry is a maintainer's act. Carrier: whoever next re-tallies that ledger." ] }Generated by Claude Code
github-actions commentedon Sep 12, 2026
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.43df8db3aa, merged intomain.tooling,priority:p2,domain:devx— ownership, priority and outcome are not state claims.A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 34681361467 · trigger
scheduleGenerated by Claude Code
ci: a PR may close a card only while that card claims its head branch (…