Filed at 2026-09-21T16:12Z by the domain:spec seat 4 (session_01AmH9bKvGoLjiY86Q4Z3og2, seat post #18917).
⛔ Filed unassigned, ⛔ no priority:*, ⛔ no domain:*, ⛔ no type — routing and grading are triage's.
⛔ Not a claim. ⛔ Not a ruling.
⚠️ This is a RECORD card, ⛔ not a gate-fix card. Nothing below asks for a gate to change behaviour.
Two of the three readings are the platform behaving as designed and one is a tool behaving as its own
comments say it should; what is missing is that none of them is written down where the next seat reads
before acting. Under ruling #202 B this is therefore ⛔ not pm:queue dispatch material as a gate
repair — it is a write into .claude/skills/pm-dispatch/references/platform-readings.md, which is a
tier-S governed surface, so it needs a seat that can land there.
⭐ Each reading below cost a live round this session. Reading 3 in particular came within one act of
being misread as 「the PR is red, hold the strip」 — which would have deadlocked the PR permanently,
because the red is the carrier and only the strip can clear it.
Reading 1 ⭐ — a PR in the merge queue reads auto_merge: null, and that is ⛔ NOT a lost arm
Measured on PR #19596 and PR #19595 this session.
| act |
reading |
PUT /pulls/19596/ccr/auto_merge while mergeable_state: blocked |
HTTP 200, {"enabled":true,"merge_method":"merge"} |
| timeline |
auto_merge_enabled at 2026-09-21T15:52:32Z, then added_to_merge_queue at 2026-09-21T15:53:32Z — about a minute later, once the state settled |
GET /pulls/19596 at 2026-09-21T16:01Z, ten minutes later |
auto_merge: **null**, merged: false, mergeable_state: clean |
| same timeline, re-read |
no auto_merge_disabled event, ever |
⇒ the queue entry replaces the auto_merge object on the PR resource. The only durable evidence of
the arm is the added_to_merge_queue timeline event.
⚠️ The trap, and this seat walked into it. A null auto_merge on a clean, unmerged PR reads
exactly like an arm that silently failed, so the seat re-armed #19596 at 16:01Z. That re-arm returned
200 and was a no-op: no new timeline event, and ⛔ no removed_from_merge_queue — the entry from
2026-09-21T15:53:32Z stayed intact. Harmless here, but it is an act taken on a false reading, and the next false
reading may not be harmless.
Second shape, same session. Arming PR #19595 when mergeable_state was already clean produced
added_to_merge_queue at 2026-09-21T16:08:59Z with no auto_merge_enabled event at all — the PR went
straight to the queue. So the event pair is not fixed:
| state at arm time |
events produced |
blocked (not yet settled) |
auto_merge_enabled, then added_to_merge_queue ≈1 min later |
clean (settled) |
added_to_merge_queue only |
⇒ the rule to write down: after arming, confirm the landing from the timeline
(added_to_merge_queue), ⛔ never from auto_merge on the PR resource. And ⛔ never re-arm on a null
auto_merge alone.
Reading 2 ⭐⭐ — the needs:contract-review carrier FORCES Clause-②: yes, so a no PR is necessarily red while it hangs
The code, scripts/check-changeset-no-major.mjs:1576, read first-hand:
if (carrier || (line?.kind === 'declared' && line.value === 'yes')) return { value: 'yes', arm, payload: true, carrier: carrierRead, readings };
⇒ the carrier alone forces yes, whatever the body says, and labels are read from the frozen
pull_request payload.
The crossed control. Five Check Changeset runs on PR #19595, all on the same unchanged head
a755473aac4018b3974315940594b704bf139c7d, toggling exactly one variable — and it flips the verdict
both ways:
| run started |
body Clause-② |
carrier on the PR |
result |
| 2026-09-21T13:47:30Z |
yes |
absent |
❌ failure |
| 2026-09-21T15:53:08Z |
no |
absent |
✅ success |
| 2026-09-21T15:54:02Z |
no |
absent |
✅ success |
| 2026-09-21T15:58:11Z |
no |
present (labeled at 2026-09-21T15:58:06Z) |
❌ failure |
| 2026-09-21T16:05:58Z |
no |
absent (stripped at 2026-09-21T16:05:53Z) |
✅ success |
⭐ Two independent things are proven by the middle rows and neither was written down anywhere:
- A PR-body edit alone turns the level axis green with no commit.
pr-automation.yml subscribes
edited, so the yes→no body fix re-fired the job on an untouched head. ⛔ Manufacturing a
synchronize commit is the workaround that trigger exists to avoid.
- Hanging the dual carrier on a
Clause-②: no PR reds Check Changeset for exactly as long as it
hangs. The gate's own comments say this is the designed order — 「THE CARRIER IS STRIPPED AT REVIEW
PASS … the human review that clears the carrier is the authority on the level」 — and 「only the
review seat can clear it」.
⇒ the rule to write down: on a Clause-②: no PR, a red Check Changeset while the carrier hangs is
the designed state. It is ⛔ not a finding, ⛔ not a reason to withhold the strip, and ⛔ no commit
can clear it — the strip is the only act that can, and pull_request is subscribed to unlabeled, so
the strip itself re-fires the job. Withholding the strip because the lane is red makes the carrier
unstrippable, which deadlocks the PR.
⚠️ This seat both caused the red (the 2026-09-21T15:58:06Z label write is the dual-carrier hang) and nearly
misread it. Recorded as this seat's, ⛔ not as a gate defect.
Reading 3 — PR-body CREATE and EDIT disagree about the attribution footer
Measured earlier this session and never written down:
| act |
body ends with a tail session-URL footer |
stored result |
POST /pulls |
yes |
byte-identical, one footer |
PATCH /pulls/{n} |
yes, the same tail |
the platform appends a second bare footer ⇒ two |
Repair shape, and it is what PR #19595's body now carries: put the session attribution in prose
(「Authored in Claude Code session … (https://…).」) and end the body with a bare footer under a
rule line. Verified on #19595 after the round-2 fix: footer count 1.
ⓘ ⛔ This is the PR-body route only. scripts/pm/post-stamped.mjs bodies already omit the footer under
the measured rule 「改侧不送页脚」 — the platform appends exactly one, a 58-byte delta, and this card's
own filing re-measured it (sent 6771, stored 6829 on the sibling card).
What the repair is
One write into .claude/skills/pm-dispatch/references/platform-readings.md carrying the three rules in
bold above. ⛔ Nothing else — no gate changes behaviour, and ⛔ no tool is being asked to.
⚠️ Check the file's line ceiling before dispatching. The sibling reference
references/contract-review.md is at 60 lines against a ceiling of 60 — headroom 0 — so a
record-only card can be unlandable for reasons that have nothing to do with its content. Whoever takes
this should read check-skill-line-ratchet.mjs against platform-readings.md first and, if the
headroom is short, say so rather than silently compressing a reading into something shorter and less
true.
Dedupe
auto_merge null merge queue added_to_merge_queue · carrier forces clause-② yes check changeset ·
needs:contract-review reds a no PR · PR body PATCH appends second footer · edited trigger re-fires check changeset without a commit · platform-readings.md merge queue arm confirmation
Generated by Claude Code
Filed at 2026-09-21T16:12Z by the
domain:specseat 4 (session_01AmH9bKvGoLjiY86Q4Z3og2, seat post #18917).⛔ Filed unassigned, ⛔ no
priority:*, ⛔ nodomain:*, ⛔ no type — routing and grading are triage's.⛔ Not a claim. ⛔ Not a ruling.
Two of the three readings are the platform behaving as designed and one is a tool behaving as its own
comments say it should; what is missing is that none of them is written down where the next seat reads
before acting. Under ruling #202 B this is therefore ⛔ not
pm:queuedispatch material as a gaterepair — it is a write into
.claude/skills/pm-dispatch/references/platform-readings.md, which is atier-S governed surface, so it needs a seat that can land there.
⭐ Each reading below cost a live round this session. Reading 3 in particular came within one act of
being misread as 「the PR is red, hold the strip」 — which would have deadlocked the PR permanently,
because the red is the carrier and only the strip can clear it.
Reading 1 ⭐ — a PR in the merge queue reads
auto_merge: null, and that is ⛔ NOT a lost armMeasured on PR #19596 and PR #19595 this session.
PUT /pulls/19596/ccr/auto_mergewhilemergeable_state: blocked{"enabled":true,"merge_method":"merge"}auto_merge_enabledat 2026-09-21T15:52:32Z, thenadded_to_merge_queueat 2026-09-21T15:53:32Z — about a minute later, once the state settledGET /pulls/19596at 2026-09-21T16:01Z, ten minutes laterauto_merge: **null**,merged: false,mergeable_state: cleanauto_merge_disabledevent, ever⇒ the queue entry replaces the
auto_mergeobject on the PR resource. The only durable evidence ofthe arm is the
added_to_merge_queuetimeline event.auto_mergeon aclean, unmerged PR readsexactly like an arm that silently failed, so the seat re-armed #19596 at
16:01Z. That re-arm returned200 and was a no-op: no new timeline event, and ⛔ no
removed_from_merge_queue— the entry from2026-09-21T15:53:32Z stayed intact. Harmless here, but it is an act taken on a false reading, and the next false
reading may not be harmless.
Second shape, same session. Arming PR #19595 when
mergeable_statewas alreadycleanproducedadded_to_merge_queueat 2026-09-21T16:08:59Z with noauto_merge_enabledevent at all — the PR wentstraight to the queue. So the event pair is not fixed:
blocked(not yet settled)auto_merge_enabled, thenadded_to_merge_queue≈1 min laterclean(settled)added_to_merge_queueonly⇒ the rule to write down: after arming, confirm the landing from the timeline
(
added_to_merge_queue), ⛔ never fromauto_mergeon the PR resource. And ⛔ never re-arm on a nullauto_mergealone.Reading 2 ⭐⭐ — the
needs:contract-reviewcarrier FORCESClause-②: yes, so anoPR is necessarily red while it hangsThe code,
scripts/check-changeset-no-major.mjs:1576, read first-hand:⇒ the carrier alone forces
yes, whatever the body says, and labels are read from the frozenpull_requestpayload.The crossed control. Five
Check Changesetruns on PR #19595, all on the same unchanged heada755473aac4018b3974315940594b704bf139c7d, toggling exactly one variable — and it flips the verdictboth ways:
Clause-②yesnononolabeledat 2026-09-21T15:58:06Z)no⭐ Two independent things are proven by the middle rows and neither was written down anywhere:
pr-automation.ymlsubscribesedited, so theyes→nobody fix re-fired the job on an untouched head. ⛔ Manufacturing asynchronizecommit is the workaround that trigger exists to avoid.Clause-②: noPR redsCheck Changesetfor exactly as long as ithangs. The gate's own comments say this is the designed order — 「THE CARRIER IS STRIPPED AT REVIEW
PASS … the human review that clears the carrier is the authority on the level」 — and 「only the
review seat can clear it」.
⇒ the rule to write down: on a
Clause-②: noPR, a redCheck Changesetwhile the carrier hangs isthe designed state. It is ⛔ not a finding, ⛔ not a reason to withhold the strip, and ⛔ no commit
can clear it — the strip is the only act that can, and
pull_requestis subscribed tounlabeled, sothe strip itself re-fires the job. Withholding the strip because the lane is red makes the carrier
unstrippable, which deadlocks the PR.
misread it. Recorded as this seat's, ⛔ not as a gate defect.
Reading 3 — PR-body CREATE and EDIT disagree about the attribution footer
Measured earlier this session and never written down:
POST /pullsPATCH /pulls/{n}Repair shape, and it is what PR #19595's body now carries: put the session attribution in prose
(「Authored in Claude Code session
…(https://…).」) and end the body with a bare footer under arule line. Verified on #19595 after the round-2 fix: footer count 1.
ⓘ ⛔ This is the PR-body route only.
scripts/pm/post-stamped.mjsbodies already omit the footer underthe measured rule 「改侧不送页脚」 — the platform appends exactly one, a 58-byte delta, and this card's
own filing re-measured it (sent 6771, stored 6829 on the sibling card).
What the repair is
One write into
.claude/skills/pm-dispatch/references/platform-readings.mdcarrying the three rules inbold above. ⛔ Nothing else — no gate changes behaviour, and ⛔ no tool is being asked to.
references/contract-review.mdis at 60 lines against a ceiling of 60 — headroom 0 — so arecord-only card can be unlandable for reasons that have nothing to do with its content. Whoever takes
this should read
check-skill-line-ratchet.mjsagainstplatform-readings.mdfirst and, if theheadroom is short, say so rather than silently compressing a reading into something shorter and less
true.
Dedupe
auto_merge null merge queue added_to_merge_queue·carrier forces clause-② yes check changeset·needs:contract-review reds a no PR·PR body PATCH appends second footer·edited trigger re-fires check changeset without a commit·platform-readings.md merge queue arm confirmationGenerated by Claude Code