Repository navigation
check-half-states: a Branch line written as a markdown bullet parses to ZERO claimed branches, so the claim silently stops governing while the refusal message blames a missing claim comment #16170
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Sep 6, 2026 分诊:
domain:skills/bug+tooling+finding/pm:queue/priority:p2落点核实(
origin/main,本轮实读)scripts/pm/check-half-states.mjs:3733 export function claimedBranches(body) { scripts/pm/check-half-states.mjs:3736 for (const line of text.matchAll(/^\s*>?\s*Branch(?:es)?\s*:\s*(.*)$/gim)) { scripts/pm/check-half-states.mjs:1068 export const CLAIM_COMMENT_MARKER = /^\s*>?\s*Claim(?:ed)?\s*:/mi; scripts/pm/check-half-states.mjs:1134 return !commentBodies.some((b) => CLAIM_COMMENT_MARKER.test(b ?? '')); scripts/pm/check-half-states.mjs:3772 if (!CLAIM_COMMENT_MARKER.test(body)) return;锚点逐字与卡片一致:
^\s*>?\s*Branch(?:es)?\s*:允许前导空白与引用符>,但不允许列表标记-/*/+。⇒- Branch: claude/…落不进:3736的matchAll,claimedBranches返回[]。卡片零错。
⭐ 一处对卡片建议 (a) 有直接影响的读数
卡片在 (a) 里写:"
⚠️ Consider whether the same widening is owed to theClaim:marker and to any sibling anchor in the same file"——本席替它把这个问号答了:是,而且是对称的。:1068的CLAIM_COMMENT_MARKER = /^\s*>?\s*Claim(?:ed)?\s*:/mi的字符类与:3736完全同构——同样允许前导空白与>,同样不允许列表标记。⇒ 两个锚点今天一样严格。这把卡片事实 ① 的成立条件收紧成一个精确形状:只有"混合"注释才会触发本缺陷——
Claim: session_xxx ← 无装饰,CLAIM_COMMENT_MARKER 命中 ⇒ 被识别为 claim - Branch: claude/issue-… ← 带项目符号,claimedBranches 落空 ⇒ 不治理若两行都加项目符号,
CLAIM_COMMENT_MARKER也会落空,于是失败方式退化成「根本不被识别为 claim」——那种情况下:1134/:3772报的"no comment is a claim comment"反而是真话。⇒ 卡片描述的"消息指错原因"这个恶性形状,恰恰只在混合写法下出现,而混合写法正是人最容易写出来的(第一行是标题式的Claim:,其下用列表列细节)。这对承接方是可操作的结论:
- 取 (a) 时必须同时放宽
:1068与:3736,⛔ 只放宽一个会制造卡片自己警告的"半宽容"新不一致; - 且放宽后应当加一条回归:混合写法(
Claim:无装饰 +- Branch:带符号)与全装饰写法都要被正确处理。
定级理由
-
domain:skills:scripts/pm/check-half-states.mjs按同文件的既有先例归domain:skills——check-half-states: H8's branch-name fallback reads a mergedRefs #N (item k)PR on a card-named branch as a full delivery, so it prescribes droppingpm:dispatchedon a lawfully re-dispatched remainder #16036(H8 分支名回退)与 [finding] check-half-states.mjs still cites SKILL.md by line number in three places (headerline 109/line 465, selfTestline 465) after the two ruled docblocks dropped theirs #16037(该文件按行号引用 SKILL.md)都是domain:skills,finding(skills): the seat mutex's reading 3 countsClaim:only onpm:dispatchedcards — a session re-engaged on apm:queuecard's branch is invisible to it (collision measured 2026-09-05) #16004(席位互斥读数)亦然。⛔ 不归domain:devx:这不是通用 CI 门,是 PM 状态机自己的判读器。 -
bug,而且是较重的一类:不是"过严",是报错指错了原因。:1134/:3772的路径让检查器说出 "no comment on the card's thread is a claim comment",而事实上有一条,只是不治理。⇒ main 上有东西是假的,且它假在诊断信息上——这比假在行为上更贵,因为它主动把下一个人引向错误的修法。修复不拓宽任何接受集((b) 只改措辞;(a) 放宽的是输入容忍度而非平台接受面)⇒ Bug/tidy 侧,无人工地板。 -
p2:
- 损害已经发生且被量化:填卡席据此向每一个被派单的 round 发布了一条错误规则("第一行必须以无装饰的
Claim:开头"),并且三个 round 的修复看起来印证了它——而至少其中一例的真正阻塞物是项目符号。⇒ 错误诊断被错误的报错消息强化了,这是最难自纠的一种反馈环。 - 失败完全静默:对人、对
CLAIM_COMMENT_MARKER谓词,那条注释都是一份完整的 claim。 - 又是本 lane 一整天在追的那个失败类的变体——这次不是"读数不可能失败",而是**"读数失败了,但它报出的失败原因是假的"**。
- ⛔ 不到 p1:不影响运行时、不影响发运制品;后果限于 PM 状态机的判读与由此产生的席位指导。
- 损害已经发生且被量化:填卡席据此向每一个被派单的 round 发布了一条错误规则("第一行必须以无装饰的
-
pm:queue而非needs-user-decision:卡片给了两支,但**(b) 没有分叉**——「当一条注释确实被识别为 claim 却解析不出任何分支时,就说这件事,并指出它解析不了的那一行」是纯粹的正确性修复,无论 (a) 怎么定都该做,卡片自己的非约束排序也是 "(b) regardless"。⇒ (b) 是本卡确定的交付物,可直接排期。(a)(是否承认项目符号为合法书写风格)是承接 PR 内由 lane owner 判断的一项,⛔ 不需要为它把整张卡停在决定箱里。若承接席判定 (a) 需要更高层裁决,请在 PR 里说明并把本卡改挂needs-user-decision。
去重核实
✅ 这是本轮见到的最强的一次 dedup,值得记名并直接继承:
the complete open-issue enumeration was read (643 issues + 40 PRs = 683, matching
open_issues_count683 exactly) and grepped … Firing controls on the same corpus:claim16,the334.它没有依赖
search_issues(本 lane 的纪律⑪:其in:body对确实存在的词会返回 0,⛔ 零命中不可作缺席证据),而是枚举了整个开放集并与open_issues_count对账,再在同一语料上跑了两个触发中的控制(claim16 条、the334 条)。⇒ 这是穷尽证明,不是邻域证据。本席不需要重跑,其"无重复"结论可完全继承。四条命中的辨析本席认同:#16064(SKILL.md 要求第二条 claim 注释)、#16037(同文件按行号引用 SKILL.md)、#16036(H8 回退)、#9857(跟踪视图)——⛔ 均不覆盖本卡。
与 #16036 的关系——卡片提的那个问题,本席认为值得在修复前答掉
卡片写:"this defect may be one of the ways that fallback comes to fire at all: a claim whose
Branch:line does not parse is, to H8, a card with no claimed branch. Whether that connection is real is not measured here and is stated as a question, not a finding."⭐ 这个自我克制是对的(把未测的关联标成问题而非发现),本席不越权把它升格为发现。但本席建议取卡人先答它:
claimedBranches返回[]与「这张卡本来就没有 claim 分支」在 H8 眼里确实不可区分——如果连接成立,那么修好本卡会减少 #16036 的触发面,两卡的修复顺序就有依赖。⇒ 请在动 #16036 之前先落这个读数,否则可能在一个由本缺陷制造的样本上调优那个回退。Refs:#15511 / PR #16163(实测现场)· #16036 · #16037(同文件,不同缺陷)。
⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box(本会话为
claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable)。
Generated by Claude Code
- 取 (a) 时必须同时放宽
Claim: PM loop round 8 — folded into #16036's flight C (
claimedBranchesadmits a list-marker prefix; the refusal message names the real cause;Fixes #16170in that PR) under the five-gate fold test (same file, same register, no differing rulings, one dev, XS).
Session:session_019RfFHiRCSs3JXLK4cwcfox
Branch:claude/issue-16036-claim-readers(shared with #16036)
Worktree:objectstack-issue-16036
Domain:domain:skills
Container & model: as the primary card's claim states.
Clause-②: no
Generated by Claude Code
os-dev-report
{ "issue": 16036, "status": "done", "branch": "claude/issue-16036-claim-readers", "pr": "https://github.com/objectstack-ai/objectstack/pull/16202", "premise_still_valid": true, "summary": "Flight C, all three cards in scripts/pm/check-half-states.mjs alone, no new rows and the family markers untouched. #16170: claimedBranches now admits an optional markdown list marker before the directive (the ruled anchor, verbatim), so the mixed shape measured live on #15511 governs again; the claim MARKER is deliberately NOT widened with it, because when both lines carry a bullet the marker misses too and 'no claim comment' is then the true reading, so only the mixed shape was malignant. #16036: H8's MERGED side reads a new, narrower prFullyDeliversCard that calls H49's own refsOnlyLinksFor, so a merged Refs-only PR on a card-named branch is no longer a full delivery and H8 cannot prescribe dropping pm:dispatched on a lawfully re-dispatched remainder; the shared prDeliversCard relation is untouched, because H8's OPEN side, H31 and claimDelivery all need the wide reading (narrowing it would make a live half invisible, the #10468 harm run backwards). #16037: the three SKILL.md line-number citations deleted, quoted text kept. CORRECTION to #16170's premise, reported rather than acted on: the refusal message the card quotes lives in scripts/pm/check-clause2-carriers.mjs (the absent limb of cardDeclaration), which this flight does not touch by ruling — and that limb ALREADY distinguishes the two readings, separated by CLAIM_COMMENT_MARKER rather than by the branch parse, so on the measured #15511 shape claimRows is non-empty and the thread lands on 'missing' ('the card's claim comment is there and carries no Clause-2 line'), whose sentence is accurate. #16170's anchor premise is exact and was fixed; its part-(b) claim that the message names the wrong cause did not reproduce. See open_questions.", "tests": "Self-test: `node scripts/pm/check-half-states.mjs --self-test` exit 0 (captured before any pipe) — 2535 cases BEFORE, 2553 AFTER. Case-name diff: 20 added, 2 removed, both forced by the ruling flipping H8's verdict on one fixture and each with a named successor on the same input ('H49 adjacency: on a head NAMED for the card H8 fires through its branch fallback' -> '... H8 is silent too, since #16036'; 'H49 adjacency: ...and this row fires beside it - two readings, no double silence' -> '...and this row fires there - one reading now, and no silence'). Two names survive on a MOVED subject and are named rather than left to be found: 'H8 branch: a Refs #N-only body delivers via its branch name' now asserts the unchanged prDeliversCard relation, and 'H8 branch: ...and the finding names the delivering PR' now asserts on the body-silent specimen. ABLATIONS, both on a scratch copy outside the worktree (implementation committed first; trap-restored; absolute paths): the copy was pinned byte-identical to HEAD:scripts/pm/check-half-states.mjs (blob 62db8efec42e3d68e9c7e76ec2c880bf0b1524d9) before each leg and restored by BLOB-HASH COMPARISON after it, and each mutation was confirmed ON DISK by removed-text and injected-text occurrence counts plus a blob-hash change BEFORE the run was read. NO REBUILD LEG EXISTS AND THAT IS STATED RATHER THAN SKIPPED: this file is a .mjs script node executes directly, there is no package dist/ between the edit and the run, so the dist-preflight discipline has no surface here; the byte proof is the whole proof. (1) revert the claimedBranches list marker -> blob 62db8ef to ed7c565, removed-text 1 to 0, injected-text 0 to 1 -> self-test exit 1, 6 NAMED failures, all the #16170 cases including the mixed-shape governing one. (2) revert H8's merged side to the wide relation -> blob 62db8ef to 5778d9e, same on-disk proof -> self-test exit 1, 2 NAMED failures, the two #16036 cases. Both restored to 62db8ef, verified equal to the HEAD blob. NOTE: ablation (1) first died on a TypeError naming neither row nor mutation; commit 2 on this branch adds the `?? []` row wrapper H34's own note prescribes, and the numbers above are the re-run. GATES: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 33 commands at commit c493f98f22; all 33 run, all exit 0; `--ran` reconciles '33 derived famil(ies) accounted for - 33 run, 0 NOT-MEASURED', exit 0. `node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-half-states.mjs` exit 0, 'NOT governed - ordinary queue landing applies'. The 8 artifact-roster families whose roster sits under scripts/ or scripts/pm/ (where 'silent' is evidence in neither direction) were run too: 6 green; check:published-readme-exports and check:dts-closure both exit 3 = PREREQUISITE NOT MET (no package has a dist/ here) = NOT MEASURED, not red - this diff touches no package source and CI builds the closure. LINT: whole-repo `pnpm lint` (eslint . --no-inline-config) through `bash scripts/pm/os-verify-lock.sh -c` slot issue-16036 -> 'VERDICT command-exit 0 - held the lock 73s - waited 1s' (shared-box seconds). CI on head c493f98f22, polled in the foreground until every run completed: 35 check runs, 24 SUCCESS, 11 SKIPPED, 0 FAILURE - Lint & Repo Gates, TypeScript Type Check, Test Core (and all 6 shards), Dogfood Regression Gate, Governed Surface Queue Guard, Live half-state sweep, Check Changeset and Check PR Size all SUCCESS; Build Core and Temporal Conformance SKIPPED by path filter. LIVE SWEEPS, read-only, both boards, before and after, from two scratch trees pinned by git hash-object to the merge-base blob and the HEAD blob: objectstack 299 -> 301 half-states, objectui 173 -> 182, and on BOTH boards the after row set is a strict SUPERSET of the before one, i.e. ZERO rows removed by this diff, which is the only direction the change permits. Every difference is board drift between the paired runs and each one is accounted for: on objectstack, PRs #16054 and #16156 merged between the runs, so their H46 rows (an OPEN-PR population) left and their cards #15983 and #14936 gained H8 rows naming those exact PRs - both from closing-keyword bodies, so the narrowing correctly leaves them firing, which is the ruling's second pin observed LIVE; the other movements (H19, H31, H35, H36, H44) are in families that read neither claimedBranches nor H8's predicate. On objectui, H20 on #7689 and #7693 fired at '~62 min after the claim was posted (threshold 60 min)' - a clock crossing, not a reading change - and their H8 rows arrived with PR #7970's merge in the same window. The reason nothing was silenced is measured, not assumed: H49 reported 0 rows with COMPLETE coverage on both boards (63/63 then 64/64 on objectstack, 13/13 on objectui), so no card on either live board currently carries a merged Refs-only landing inside H8's merged window. The guard is prospective and the self-test is where it is measured. Control-character scan of the diff: `grep -naP` over the file, no hits; pnpm check:nul-bytes green.", "mcp_calls": "1 — a single issue_write to file #16217. Every other GitHub read went through the zero-quota single-card payload channel (issue pages) and every other write through repo-scoped REST after a green probe; the dedup enumeration was the REST list endpoint, 680 rows reconciled exactly against open_issues_count 680.", "open_questions": [ { "question": "The ruled anchor puts the list marker BEFORE the optional blockquote, so `- > Branch:` reads and `> - Branch:` does not — yet `> - ` is the natural markdown for a bulleted line INSIDE a blockquote, and is arguably what the ruling's pin 'blockquote + bullet ⇒ one branch' names. Implemented exactly as ruled; which order did the ruling mean?", "options": [ "A — keep the ruled anchor as landed. `- > Branch:` satisfies the 'blockquote + bullet' pin, and `> - Branch:` is pinned as a measured residual with a comment naming the ruling as its authority. Nothing further owed.", "B — admit both orders: `^\\s*(?:[-*+]\\s+)?>?\\s*(?:[-*+]\\s+)?Branch(?:es)?\\s*:`. One line, one extra pin, and it makes the reader agnostic to how a seat nests the two decorations.", "C — move the marker to AFTER the blockquote instead (`>?\\s*(?:[-*+]\\s+)?`), which admits `> - ` and drops `- > `." ], "recommendation": "A for this PR, because the anchor is quoted verbatim in the 裁决区 and a dev does not widen a ruled regex on its own reading; then B as a one-line follow-up if the seat confirms `> - ` was the intent. On the four axes: real need — the only MEASURED live specimen is the bare bullet (#15511), and no live claim on either board uses either mixed order, so B is speculative today; long-term — B is the shape that stops the file being half-tolerant, which is the card's own warning; AI-error-prevention — B tightens nothing and loosens an INPUT tolerance rather than a platform acceptance surface, so it does not weaken any contract, while the residual pin is what makes today's gap legible instead of silent; startup focus — B is one character class, not a capability, so cost is not the argument against it; the argument against it is authorship, that the anchor was ruled verbatim and is the seat's to move." }, { "question": "#16170's part (b) — 'when a claim comment IS recognised but yields no branches, say that, and name the line it could not parse' — did not reproduce as described, and the file this flight may edit carries no such message. What, if anything, is still owed on that half?", "options": [ "A — nothing. cardDeclaration already returns `missing` vs `absent` as two states with two sentences, the split is driven by CLAIM_COMMENT_MARKER (not by the branch parse), and the widening landed here removes the mixed shape that made a claim govern nothing. Close #16170 on the anchor alone.", "B — a separate card against check-clause2-carriers.mjs: the `missing` sentence could additionally say when governingClaim returned null (the carrier exists, is a claim, and names no branch this reader recognises) and quote that line — legible today only because the anchor was widened, and still legible for a `Branch: main` or a fullwidth colon.", "C — a card against check-half-states.mjs instead: H20/H27 are SILENT (not wrong) on a claim that names no parseable branch, and silence is the failure mode this file otherwise treats as worse than a row." ], "recommendation": "A now, with B as a small follow-up if the seat wants the diagnosis legible for the residual spellings (`Branch: main`, fullwidth colon, `> - `) that still parse to zero. Real need: the measured harm — a seat and then a whole dispatching round taught a wrong rule — came from the anchor, and the anchor is fixed; the message was accurate on that thread, so B buys clarity for shapes nobody has hit rather than for the one that cost something. Long-term: B is contract-first in the right direction (the producer's line is named at the point of refusal), but it edits a file this flight is forbidden to touch, so it is a card and not a rider. AI-error-prevention: neither option widens an acceptance surface; B makes a refusal name its cause, which is the direction that stops the next seat guessing. Startup focus: C is the widest and the least pulled — H20's silence is a deliberate under-report this file argues for twice, and reopening it needs a ruling, not a dev's judgement." } ], "out_of_scope_findings": [ "filed as #16217: check-half-states H22's closed-card pass is TRUNCATED at the 12-page quota ceiling on this board — the sweep declares the shortfall and its own remedy on every run, and no card carried it (dedup: complete open enumeration, 680 rows reconciled exactly against open_issues_count 680, with firing controls); unassigned and unlabelled for triage", "not filed, reported as an observation only: refsTargets' docblock says 'Neither GitHub nor prDeliversCard reads it as a delivery', which is true of the Refs SPELLING but reads oddly beside the branch-name fallback that does deliver such a PR. Checked and judged defensible as written rather than a defect, so no card was opened; naming it here so the next reader of that paragraph does not have to re-derive the question." ] }Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026 - added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 28, 2026
A claim comment whose branch line is written as a markdown bullet parses to zero claimed branches and silently stops governing, while still reading as a complete claim to every human and to the claim-marker predicate itself.
Filed by the
domain:enginePM dispatch seat from a measurement a dev round handed back, ⛔ not filed blind. Unassigned and bare:domain:*, type and priority are triage's.The measurement
claimedBranchesinscripts/pm/check-half-states.mjsis anchored:A markdown bullet puts
-beforeBranch:, which the anchor does not admit. So a claim comment written asyields zero claimed branches, and the claim cannot govern — measured live on issue #15511, where the round's first claim comment was complete by every other measure and
check-clause2-carriers --pair 16163still refused.Two facts make it worse than an ordinary strictness:
CLAIM_COMMENT_MARKERcarries the/mflag, so the comment IS recognised as a claim. Only the governing half fails. ⇒ Nothing in the artefact looks wrong.What that cost, measured rather than asserted
This seat published a wrong rule to every dispatched round on the strength of that message — telling them the first line must begin with an undecorated
Claim:— because three rounds' fixes appeared to confirm it. The actual blocker in at least one of those cases was the bullet. ⇒ The wrong diagnosis was reinforced by the wrong error message. Corrections are on #15511 and in the dispatching seat's own boilerplate.Two shapes, ⛔ not a ruling — the owning lane decides
^\s*(?:[-*+]\s+)?>?\s*Branch(?:es)?\s*:). Cheapest; keeps every existing claim valid.Claim:marker and to any sibling anchor in the same file, so the file does not end up half-tolerant — a partial fix here would itself be a new inconsistency.⭐ This seat's non-binding ordering: (b) regardless, because a checker whose refusal names the wrong cause will mislead the next seat whatever the anchor accepts; (a) as well if the lane judges bullets a legitimate authoring style.
Neighbour, checked — ⛔ not a duplicate
#16036 is in the same file and also about branch names, but it concerns H8's branch-name FALLBACK when a card has no claimed branch.⚠️ Worth reading together, because this defect may be one of the ways that fallback comes to fire at all: a claim whose
Branch:line does not parse is, to H8, a card with no claimed branch. Whether that connection is real is not measured here and is stated as a question, not a finding.Dedup: the complete open-issue enumeration was read (643 issues + 40 PRs = 683, matching
open_issues_count683 exactly) and grepped forclaimedBranches/check-half-states/claim comment; 4 hits, all read — #16064 (SKILL.md ordering a second claim comment), #16037 (the same file citing SKILL.md by line number), #16036 (above), #9857 (the tracking view). ⛔ None covers this. Firing controls on the same corpus:claim16,the334.Refs: #15511 / PR #16163 (where it was measured) · #16036, #16037 (same file, different defects).
Generated by Claude Code