Uh oh!
There was an error while loading. Please reload this page.
fix(pm): complete pm label vocabulary (pm:seat, priority:p0) and add desc-cap guard - #10116
Conversation
Both are rows of the pm state model (SKILL.md "State model") with no `gh label create` entry anywhere in ensure-pm-labels.sh, so on a repo where the label object does not exist yet the first application has to remember to create it and a rerun of the script can never repair the gap. Placement is measured, not assumed: - `priority:p0` -> the FOUR-REPO loop. Its consumers are the lane pull order and the half-state sweep's H10/H11, and that sweep is repo-parameterized (PM_SWEEP_REPO). Measured 2026-08-20: objectui carries a live priority:p0 card whose label object was auto-created by that first application, with GitHub's default colour and an empty description. - `pm:seat` -> the MAIN-REPO-ONLY section. There is exactly one seat post per `domain:*` / `repo:*` lane and both families are main-repo-only, so the `label:pm:seat` index can only be read here. Measured 2026-08-20: zero pm:seat cards in objectui and objectos. Neither label is named in the header's deliberate-absence records (the retired lanes; the objectos lane/target exclusions), so the absence was drift. Both descriptions are within the 100-character cap the header warns about (95 and 92 characters). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd
scripts/pm/ensure-pm-labels.sh documents a silent failure mode that nothing enforced: GitHub hard-caps label descriptions at 100 characters, `gh label create` 422s above that, and the `|| true` that makes the script rerunnable swallows the 422 — so an over-long -d means the label is NEVER created on a repo where it does not exist yet, and a rerun can never repair it. The only enforcement was the author counting characters by hand, which has already been paid once by a PR that trimmed the descriptions that had drifted over. Headroom is thin, so this is not theoretical: measured on this branch, the longest live description (repo:objectui) is exactly 100 characters. The measure is CHARACTERS, never bytes: the live needs:contract-review description is 97 characters / 101 bytes and is accepted by GitHub, so a naive byte-length guard would red a label that works. Length is counted in Unicode code points rather than UTF-16 code units, so an astral character counts once. Shell expansion is part of the measurement: a description interpolating a loop variable is measured after substituting that variable's longest value, because what GitHub receives is the expanded string. A variable the checker cannot resolve is red, not skipped. Two defences against a parser that has silently stopped matching: a LOWER bound on the number of descriptions found (not a ratchet — a "the parse still finds the file" assertion), and a self-test that puts a fake `gh` on PATH, runs the real script, and checks the descriptions it was actually invoked with against the ones parsed statically. The script has no dry-run mode of its own, which is the other half of why nothing measured this before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32333782612 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32333809661 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32334243432 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32334639902 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32335265453 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32335487463 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32335889263 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes#10098
Scope was expanded by consolidation. The gate half's spec authority is #10099 — a consolidated duplicate of this card, no longer open. Two halves, one commit each.
1. Vocabulary lines (
3ca1b8a)pm:seatandpriority:p0are rows of the pm state model (SKILL.md "State model") with nogh label createentry anywhere inscripts/pm/ensure-pm-labels.sh. On a repo where the label object does not exist yet, the first application has to remember to create it, and a rerun of the script can never repair the gap.Placement was measured, not assumed — the card left this explicitly unpinned, so both readings were checked against live consumers rather than argued from shape:
priority:p0scripts/pm/check-half-states.mjs), and that sweep is repo-parameterized (PM_SWEEP_REPO). Measured 2026-08-20: objectui carries a livepriority:p0card (objectui#5304).pm:seatdomain:*/repo:*lane, and both families are main-repo-only, so thelabel:pm:seatindex can only be read here. Measured 2026-08-20: zeropm:seatcards in objectui and objectos.The objectui
priority:p0label object exists but was auto-created by that first application: GitHub's default colouredededand an empty description. That is exactly the drift this row closes for the next repo. Creation stays create-if-missing, so objects that already exist keep their current colour and description; aligning those is a separate deliberate PM action, stated in the comment block the same way the retired label objects are.Neither label is named in the header's deliberate-absence records (the retired lanes; the objectos lane/target exclusions), so the absence was drift rather than intent — the card's second open question, answered.
Both descriptions are within the cap: 95 and 92 characters.
Boundary: objectstack-ai/cloud is not reachable from this session, so the cloud half of each measurement is unverified. It does not change either placement —
priority:p0goes in the loop that already includes cloud, andpm:seat's main-repo-only argument rests on the lane families, not on a cloud card count.2. Desc-cap guard gate (
0efb508)New
scripts/pm/check-label-desc-cap.mjs, wired ascheck:pm-label-desc-capand run unconditionally inlint.ymlbeside the other self-tested pm gates.GitHub hard-caps label descriptions at 100 characters;
gh label create422s above it, and the|| truethat makes the vocabulary script rerunnable swallows the 422. The script header documented all of this and nothing enforced it. Headroom is thin: the longest live description is exactly 100 characters (repo:objectui).Design points that are load-bearing:
needs:contract-reviewdescription is 97 characters / 101 bytes and is accepted by GitHub. Measured leg: pointing the same parse at a byte length reds exactly that one live, working label, while the shipped character guard is clean. Counted in Unicode code points, not UTF-16 code units, so an astral character counts once.ghon PATH, runs the real script, and checks the descriptions it was actually invoked with against the ones parsed statically. That dry-run idea comes from [finding] ensure-pm-labels.sh documents a silent 100-char label-description failure mode with no mechanical guard behind it #10099's verification note; the script has no dry-run mode of its own, which is the other half of why nothing measured this before.The gate self-routes:
dispatch-gates.mjsmatches it for a card touchingscripts/pm/ensure-pm-labels.shthrough its own module-body path constant.Reverse verification
Each leg's direction was predicted in writing first, then observed.
repo:objectui's description, 100 to 101. Gate exit 1:repo:objectui — description is 101 characters, over GitHub's 100-character cap. The self-test went red independently on two cases, including the fake-ghleg (no description GitHub would 422 reaches the fake gh) — the runtime path, not only the static parse. Restored byte-identical:git hash-objectequals the HEAD blob shaec076de35b768d434c7e5c5efb1c225dce11e8c5.needs:contract-review (97 chars / 101 bytes); the character guard reds nothing.ghon PATH, real script:priority:p0invoked 4 times, one per repo;pm:seatinvoked once, main repo only. Baseline leg on theorigin/maincopy of the script: 48 create calls, 0 and 0. After: 53.One self-test expectation of mine was wrong on the first run and the self-test caught it (
v17/v18are the same length, so the longest-value tie keeps the first) — the case now pins the tie-break behaviour instead.Gates
Union derived from the real diff with
node scripts/pm/dispatch-gates.mjs(no paths — the script takes its own change set off the merge base), at0efb508, after the final commit:Green:
check:pm-label-desc-cap,check:node-version,check:required-contexts,check:workflow-status-functions,check:shard-attestation,check:cross-package-test-inputs,check:type-check-coverage,check:pm-dispatch-gates,check:nul-bytes.bash -non the shell file: clean. Control-byte scan of every edited file: clean.check:type-check-debtexits 1 in this worktree on a build-state precondition, not a verdict:--re-measure cannot run: 55 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk. Control run: revertinglint.ymlandpackage.jsontoorigin/mainin place reproduces the identical error, so it is the unbuilt worktree, not this diff. CI builds before it runs.No changeset:
scripts/**,.github/workflows/**and a rootpackage.jsonscript entry publish nothing.Generated by Claude Code