Skip to content

feat(pm): flag a Part of PR whose body also carries a closing keyword for the same card (#8293) - #8475

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-8293-partof-autoclose-mechanism
Aug 13, 2026
Merged

feat(pm): flag a Part of PR whose body also carries a closing keyword for the same card (#8293)#8475
hotlong merged 1 commit into
mainfrom
claude/issue-8293-partof-autoclose-mechanism

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Part of #8293

⚠️Deliberately Part of, not the closing keyword. The suppression half of this card is protocol text outside scripts/pm/** and is left open on purpose — see "What is NOT in this PR". What lands here is the investigation result plus the report-only detector.

The card's diagnostic premise is falsified, and that correction is the main deliverable. The close was performed by GitHub's ordinary closing-keyword parser reading the PR body. It was not a development-sidebar link.

The mechanism, measured

PR #8277's body opened with Part of #8131 and then carried this sentence — the one written to prevent the auto-close:

Merging this must not auto-close a card with that half unaddressed; the PM should close 8131 deliberately once 8136 lands.

⚠️ Issue numbers de-hashed in that quote on purpose: reproducing the literal hash after the keyword would re-close the very card this PR is about.

The keyword plus the reference is all GitHub's parser reads. It ignores the modal "should", ignores the negation in the clause before it, and ignores the entire paragraph arguing the card must stay open. The card's author scanned for closing keywords and reported none, because the scan was looking for a sentence asserting closure — not for a warning against it.

Four live readings pin the parser's actual behaviour. Each is a self-test fixture:

#ReadingOutcome
1keyword + reference in prose — PR #8277 and the half-delivered cardclosing link created; card closed 2 s after the merge
2the same body's other reference, one clause later behind the word "once", no keywordno closing link; survived the merge untouched
3Part of + reference alone — PR #8261, the same round's other partial deliverystayed open, exactly as the protocol intends
4keyword + reference inside inline backticks — open PR #8454no closing link

Reading 2 is what refutes the sidebar hypothesis. One body, one merge, two issue references, opposite outcomes — and the second card's closed_by_pull_requests is empty while the first names the PR. No development-link theory discriminates between two references in the same sentence. The keyword parser does, precisely.

Reading 3 dissolves the non-uniformity the card flagged as its strongest lead: the other Part of card stayed open because its PR body simply contained no closing keyword. Nothing else differed.

Reading 4 changed the implementation. Open PR #8454 says "the dispatch asked for Fixes ..." inside backticks while its card carries no closing link at all — so GitHub does not fire inside a code span. A body regex without that step would have flagged that PR, which is correct. Stripping markdown code is what keeps this guard off the careful authors it would otherwise punish.

What landed

H7 in scripts/pm/check-half-states.mjs — report-only, in the file's existing character:

  • Flags an open PR whose body declares Part of a card while also carrying a closing keyword bound to that same card. Contradictory by construction.
  • Bound per issue number. A PR that is Part of card A and legitimately closes card B is a normal shape and stays clean — open PR ADR-0123 + enforcement: an authenticated session with no active organization — reads resolve to nothing, tenant-scoped writes are refused loudly #8471 is the live specimen.
  • Strips markdown code before scanning (reading 4). Inline spans are measured; fenced blocks follow the same rule but were not independently measured, and that asymmetry is stated at the function rather than hidden.
  • Gerunds are not keywords. GitHub's list has no "closing"/"fixing", and both are everywhere in exactly this prose.
  • The keyword binds only on the same line, which is what stops a keyword before a stripped code block from splicing onto a reference after it. The self-test pins that splice; it was a real bug caught by that case during implementation.

This is the only item in the file that reads pull requests rather than issues, because the PR body is the surface where the fact is still fixable. Drafts are included — a draft is where this is cheapest to fix.

Verification

  • node scripts/pm/check-half-states.mjs --self-test79 cases pass (58 before; +21 for H7). Exit 0.
  • Live sweep and --probe exit 3 (PREREQUISITE NOT MET) in this container both before and after the change — the file's own documented "class 3" transport verdict, unchanged by this diff. This container cannot run live mode, which the script says in its header.
  • Live-corpus validation: the shipped predicate run over all 15 open PRs — 3 declare Part of, 0 findings. No false positives on the real board.
  • Reverse verification, direction predicted before each run:
AblationPredictedObserved
stripMarkdownCode made a pass-throughthe backticked-PR case + the strip cases go red5 red, exactly those
gerund admitted into the keyword listthe two "not a keyword" cases go red2 red, as predicted
per-number binding collapsedthe Part of A + closes B case goes red1 red, as predicted

A first attempt at the strip ablation edited only one of two call sites and produced 0 red — recorded here because that is precisely the false-green shape an ablation must not be allowed to have. It was redone at a single point that certainly ablates.

  • pnpm check:nul-bytes — green (7637 files). npx eslint scripts/pm/check-half-states.mjs --no-inline-config — clean.
  • Gate re-derivation against the actual changed path: node scripts/pm/dispatch-gates.mjs scripts/pm/check-half-states.mjs reports no check family naming this path, confirming the dispatch's judgment set. Nothing added.
  • This PR's own body was run through the shipped predicate before it was opened: no finding.

What is NOT in this PR

  • Suppression at source. The source is author-written body text, so suppressing it means a protocol rule telling authors to keep closing keywords away from other cards' numbers. That is skill/reference text outside scripts/pm/** — reported, not implemented, per the card's scope boundary. This is the half that keeps this PR Part of rather than a closing declaration.
  • The landing-protocol line (references/landing-operations.md), moved to the references sweep card at claim time.
  • Making this a blocking gate. The file is report-only by a documented argument, and board state must not fail an unrelated PR's CI.
  • No CI-workflow, GitHub-App or automation changes.

Generated by Claude Code

…ord (#8293)
The close mechanism was GitHub's closing-keyword parser matching a
sentence written to PREVENT the auto-close, not a development-sidebar
link. H7 detects that contradiction on open PR bodies, report-only.
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 2:57pm

Request Review

@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Aug 13, 2026 — with Claude
@hotlong
hotlong marked this pull request as ready for review August 13, 2026 15:38
@hotlong
hotlong added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit f69710cAug 13, 2026
27 checks passed
@hotlong
hotlong deleted the claude/issue-8293-partof-autoclose-mechanism branch August 13, 2026 16:08
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@hotlong@claude