Skip to content

fix(pr-merge): clear a native gate's needs-human label once its cause is gone - #10198

Merged
kyle-sexton merged 13 commits into
mainfrom
fix/clear-native-escalations
Oct 26, 2026
Merged

kyle-sexton merged 13 commits into
mainfrom
fix/clear-native-escalations

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

No related issue: needs-human audit row P7 (owner rule 2026-10-26)

Head: ecf1fda804. Owner Q-CLEAR B (strict, 2026-10-26).

Summary

This repository runs merge.lane-prs: native (docs/conventions/pr-pipeline.yaml). Under native, every pr-merge
needs-human label on a lanes App pull request comes from enable-auto-merge.mjs (note(), through verifyClears and
refusalWrites). #9826 clears such a label once the
head moves (head-moved). A label set before its cause became a lane's (for example the evidence-unbound risk hold on
#10090) stayed until a person removed it, even
though the lanes now clear that cause.

Fix

clear-escalations.mjs re-judges enable-auto-merge's own person refusal comment at the head it was raised at, but only
when all of these hold:

  • LANE_PRS is native and the PR is the lanes App's.
  • The comment is the only pr-merge escalation on the PR, names the current head, is unedited, and equals
    refusalBody(true, …).
  • Its recorded cause is lane-class: every ; -separated code is on LANE_CAUSES for its kind (owner Q-CLEAR B).

Then the gate's reader runs (pythonVerify, the same evidence call enable-auto-merge makes, rung cap passed as C3),
and the label comes off as refusal-cleared when nothing refuses, or when what refuses now is lane-class too and the
reader got at least as far as the recorded cause (link, then verdict, then risk score).

Owner Q-CLEAR B: a person-class recorded cause is never read and never lifted at its head, even when the reader would
find nothing refusing. That covers an issue link (every one), security-finding, trust-gate:*, cost-review-required,
high-path:*, verify:untrusted-spec, verify:repeated-fail, verify:too-old, verify:ineligible, and any unknown
code. A person removes the label, or main's head-moved clear does once the head moves (enable-auto-merge labels the new
head again if the cause still holds). Because person-class labels no longer depend on the reader's answer, they do not
depend on the fixed C3 rung or the demoted=False read either.

LANE_CAUSES is an explicit per-kind list, so it fails closed. babysit_risk.PERSON_CODES (#10249) is to become its
single source.

  • link: none.
  • verdict: verify:fail, verify:earlier-fail, verify:spec-changed, which a push clears.
  • risk: files-incomplete, evidence-unbound:<lane>, manifest-review-required. Codes match whole; only
    evidence-unbound: is a prefix.

Reads are capped at NATIVE_READS_PER_RUN (3) a run, starting at rotated(held, GITHUB_RUN_NUMBER), so labels past the
cap are read on a later run. In pr-merge.yml the clear step has a 15-minute timeout and gets the reader's Actions token
plus the gate step's BABYSIT_VERIFY_LISTING, BABYSIT_VERIFY_HEAD_REFS and BABYSIT_RUN_CACHE. The merge job's
ceiling rises from 30 to 45 minutes to fit the clear's three reads of up to four minutes each beside the gate's budget
(workflow.test.mjs checks it).

In enable-auto-merge.mjs, READER_HOLDS exports the reason prefixes verifyHold writes. The person refusal wording
stays main's ("fix the reason, then remove the label"): every label enable-auto-merge sets is person-class
(verifyClears), so the same-head auto-removal wording an earlier round of this PR added is reverted.

Changelog: .changes/pr-pipeline/fix-clear-native-escalations-6e1b4a92.md. README: a refusal-cleared row in the
escalation-clear table.

Verification

At ecf1fda804, with origin/main merged at 14fd4e5aa9 (no rebase):

  • Fail-first: a person-class label is never removed at its head, even when nothing refuses fails on the previous head's
    source (6433994e), which read and lifted a person-class label when nothing refused.
  • Mutant: restoring the null shortcut for every kind (disabling the lane-class gate before the read) fails that test.
  • a lane-class label comes off at its head once nothing refuses and a lane-class label stays when the reader's answer no longer shows its cause cover the lane side.
  • node --test .github/scripts/pr-merge/*.test.mjs: 434 passed, 0 failed.
  • actionlint .github/workflows/pr-merge.yml: clean. scripts/check-affected.sh --tests: 10 passed, 4 skipped.

Related

  • #10090: its evidence-unbound risk label comes
    off once a whole read names only lane causes.
  • #9826: merged. Its head-moved rule handles a
    refusal at an old head, person-class included.

🤖 Generated with Claude Code

kyle-sexton and others added 2 commits October 25, 2026 23:42
… is gone

Under merge.lane-prs native, enable-auto-merge writes every lanes App PR's
pr-merge needs-human label, and its holdReason then keeps the PR from being
judged again, so the label stayed until a person removed it (#10090).

clear-escalations now judges enable-auto-merge's own person refusal comment
(refusalBody, byte for byte) again at the current head with the gate's
reader, and removes the label as refusal-cleared only when nothing refuses or
what refuses is a lane's (verifyClears lane). A person's refusal, a risk hold
(security finding, trust gate, cost), an unread answer, a failed read, and a
hold-path or cost-path refusal keep it. Labels a person set and other lanes'
escalations are untouched, as before. At most three reads a run; the step
gets the reader's Actions token and a 15-minute timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 26, 2026 05:25
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T05:36:56.239300Z 8107219 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8107219b59

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/scripts/pr-merge/clear-escalations.mjs
@kyle-sexton
kyle-sexton marked this pull request as draft October 26, 2026 05:35
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 26, 2026 05:35

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8107219b59

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/scripts/pr-merge/clear-escalations.mjs Outdated
@kyle-sexton kyle-sexton added the do-not-merge Hard merge gate: do not merge while applied. label Oct 26, 2026
kyle-sexton and others added 2 commits October 26, 2026 05:27
…lations

# Conflicts:
#	.github/scripts/pr-merge/clear-escalations.mjs
…ep a hidden cause

clear-escalations no longer clears a risk or issue-link label on a reader
answer that refuses for another reason (a verdict refusal hides the risk
score), and never clears a verifier label on an unreadable or missing
verdict or an unreadable risk score. It re-judges enable-auto-merge's
refusal only under merge.lane-prs native, only at the head the comment
names (a moved head takes main's head-moved path), and not when the
comment was edited. The sweep starts at an offset that moves with the run
number, so labels past the three-read cap are read on a later run, and the
clear step shares the gate step's run cache, listing and head-ref map.

enable-auto-merge exports READER_HOLDS, the reason prefixes verifyHold
writes, and tells a person that pr-merge takes a reader refusal's label off
itself; a comment in the earlier wording still stands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed ecf1fda80410ad8e5aab76aa923e4c00c314a18c: 0 finding(s), 0 as inline comments.

Incremental security review of the five files changed since 6433994. I found no security issues.

critical-findings: 0

What I checked:

  • clear-escalations.mjs (merge-hold control). The change narrows what the script lifts. Under merge.lane-prs: native, a label whose recorded cause is a person's no longer gets a reader run or an early lift. That covers an issue link, a security finding, a trust-gate widening, a cost hold, a high path, an untrusted spec, the repeated-fail cap, and any unknown code. laneCause fails closed. The link list is empty, so every link reason counts as a person's. A code must match a LANE_CAUSES entry whole, apart from the evidence-unbound: prefix. A recorded cause the script does not recognise returns early with "one a person clears" before reads.left is decremented or verify is called. liftsRefusal now checks only the current reason, because the caller has already vetted the recorded one. The reader-order check and the "unreadable or failed read keeps the label" paths are unchanged.
  • nativeRefusal and refusalBody (enable-auto-merge.mjs). The script drops the earlier-wording comment variant (personBodies, readerWho). A comment in the older wording no longer matches refusalBody, so nativeRefusal returns null. It then reaches the a pr-merge escalation names the current head branch and keeps the label. That fails closed. It never lifts a hold that was lifted before.
  • refusalWrites simplification. For a standing person comment in the current wording, the put-back and liftedSince behaviour is the same as before. A comment in the earlier wording now gets a fresh escalation comment, which is harmless.
  • Test, docs and changelog changes. These only describe or exercise the narrowed behaviour.
  • No trust-control widening. The diff does not touch who is trusted, token scope, the kill switch or the broker. It does not weaken any hold. No pr-merge.yml change appears in the incremental diff.

No manifest files changed. No instruction-surface deletions apply: the removed text is a code comment and a wording variant, and the narrowing code still enforces the rule.

Spend: the clear step's timeout goes from 3 to 15 minutes and the merge job's from 30 to 45 minutes. This uses only subscription or prepaid runner time, so it is not a hold.

@github-actions github-actions Bot added the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

claude-security-review holds this pull request for a person (critical-security) at 9775cddaca4637e82ea1d0c1215425a80c294c21.

clear-escalations.mjs (with the pr-merge.yml step that runs it) now removes the human-gated needs-human label on its own, from the gate reader's output. That narrows a merge hold, which this lane treats as CRITICAL. A person should confirm the narrowing is intended.

claude-lane: claude-security-review

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed ecf1fda80410ad8e5aab76aa923e4c00c314a18c: 0 finding(s), 0 as inline comments.

Incremental review of the changes since 6433994. I found nothing to flag. I read the code and did not run the test suite.

What changed. Under merge.lane-prs: native, clear-escalations.mjs now reads the gate again only when the recorded refusal cause is one the lanes clear (every code on LANE_CAUSES). A person's recorded cause (an issue link, a security finding, a trust-gate widening, a cost hold, a high path, an untrusted spec, the repeated-fail cap, an unknown code) is kept at its head. Only a person removing the label, or the head-moved clear, lifts it.

Checked, no issue:

  • Early return before the read budget. The "one a person clears" check (clear-escalations.mjs:344) runs before reads.left is spent. A person-class label therefore costs no reader run. The link kind has an empty LANE_CAUSES list, so it takes the same early return.
  • liftsRefusal. The recorded-cause check moved to the caller, and liftsRefusal now compares only the position of the current refusal against the recorded one and whether the current refusal is a lane cause. The behavior matches the old two-sided check.
  • Reason string in the log. The new reason string embeds the recorded refusal text. That text passes through escapeCommand in verifyHold and the control-character replacement in refusalBody when it is written, so kept: … at line 427 cannot inject workflow commands.
  • refusalWrites in enable-auto-merge.mjs. The merged condition at lines 580–582 behaves the same as the old two branches for the single wording that remains. A standing person comment still gets the label put back with liftedSince. A standing lane comment still writes nothing.
  • Removed earlier wording. Dropping personBodies and the earlier wording makes nativeRefusal return null for a comment in the old shape. The path then ends at "a pr-merge escalation names the current head" and keeps the label. That fails closed. No other reference to personBodies, readerHold, readerWho or personWho remains in the repository.
  • Tests. The tests cover each person-class reason against three reader answers and assert no read and no write. They also cover the lane-class lift and the cases where a lane label stays. The deleted earlier-wording test matches the removed behavior.
  • Docs and fragment. The README row and the change fragment agree with the code. The pr-merge.yml timeout bumps they mention are in the full diff (merge job 30 to 45, clear step 3 to 15), and workflow.test.mjs checks them against the step sum.

Note, not counted as a finding. A person-class refusal whose cause goes away at the same head, for example an issue link added to the PR body, now needs a person to remove the label by hand. The PR states this as owner decision Q-CLEAR B.

Spend. The change does not add a paid charge or any lane spend. The escalation clear step's timeout rises from 3 to 15 minutes, at most three reader runs per run, within existing Actions usage.

…ue link

The reader answers an ineligible link early, with verify:ineligible and no
verdict, so a lane-class link answer no longer lifts a verifier label. The
reader-refusal wording now says the same-head clear happens only under
merge.lane-prs native, since enable-auto-merge also runs under gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 9d04e31666156a8df67f1670c2520d2fb020b623; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread .github/scripts/pr-merge/clear-escalations.mjs Outdated
kyle-sexton and others added 2 commits October 26, 2026 06:17
A lane-class reader answer can hide a cause a person must clear: an unbound
security review never reads its findings, a partial file listing never
checks cost, trust or high paths, and a verdict that is not a pass never
checks the spec's trust. A label whose recorded reason carries a
PERSON_RISK code or a PERSON_REFUSALS verdict now comes off only when the
reader refuses nothing; the lane-answer relaxation stays for recorded
causes the lanes clear, such as #10090's evidence-unbound label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-security-review: 1 inline finding(s) on 149f71af87796e1b5f78b9fc925eed474d2a843d; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

Comment thread .github/scripts/pr-merge/clear-escalations.mjs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

claude-security-review holds this pull request for a person (critical-security) at 149f71af87796e1b5f78b9fc925eed474d2a843d.

Trust-control change: clear-escalations.mjs (with enable-auto-merge.mjs and the pr-merge.yml clear step) now lets the pr-merge job remove a person-class needs-human label (security-finding, trust-gate, cost-review-required, high-path:**, verify:untrusted-spec, verify:repeated-fail) on its own, after one same-head reader read with no refusal. The next enable-auto-merge run can then arm auto-merge with no person having cleared the cause. This narrows a merge hold, so the review lane treats it as CRITICAL.

claude-lane: claude-security-review

kyle-sexton and others added 2 commits October 26, 2026 07:05
…icit lane list

clear-escalations now treats a recorded refusal as a lane's only when
every "; "-separated risk code, or the verdict, is on LANE_CAUSES: the
holding lane codes babysit_risk.py emits (files-incomplete,
evidence-unbound:<lane>, the manifest review), the reader's unread score,
and every verifier refusal but untrusted-spec and repeated-fail. Any other
code, an unknown one or a configured high path included, and every issue
link, comes off only when the reader refuses nothing. The check no longer
reads enable-auto-merge's PERSON_RISK or PERSON_REFUSALS, so it fails
closed whatever those lists become.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lations

# Conflicts:
#	.github/workflows/pr-merge.yml

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on c2384244d8d4d70cd8b8594ae90cd7698aa8dd71; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread .github/workflows/pr-merge.yml

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-security-review: 1 inline finding(s) on c2384244d8d4d70cd8b8594ae90cd7698aa8dd71; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

Comment thread .github/scripts/pr-merge/clear-escalations.mjs
@github-actions

Copy link
Copy Markdown
Contributor

claude-security-review holds this pull request for a person (critical-security) at c2384244d8d4d70cd8b8594ae90cd7698aa8dd71.

Trust-control change: .github/scripts/pr-merge/clear-escalations.mjs (with enable-auto-merge.mjs and the pr-merge.yml clear step) now removes enable-auto-merge's needs-human label without a person under merge.lane-prs: native. It does this on a lane-class answer even while the reader still refuses, and it lifts verdict-kind labels without reading the risk score. That narrows a merge hold, which the security-review skill classes as CRITICAL. A person should confirm the narrowing is intended.

claude-lane: claude-security-review

… recorded one

A label now lifts on a refusing answer only when every code recorded and
every code refusing now is on LANE_CAUSES for its kind, and the reader got
at least as far as the recorded cause (link, then verdict, then risk).
The list is per kind with no branches: issue links have none, verdicts are
FAIL, EARLIER_FAIL and SPEC_CHANGED (too-old recurs at the same head,
ineligible always comes with a link answer, missing and unreadable are no
answer), and risk codes are files-incomplete, evidence-unbound:<lane> and
the manifest review. The reader's own unread link or score is no longer a
lane cause, and a current high-path or other person code keeps a recorded
lane label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton kyle-sexton removed the do-not-merge Hard merge gate: do not merge while applied. label Oct 26, 2026
The merge job's 30-minute ceiling covered the gate's sweep alone; the
escalation clear now takes up to 15 minutes for its three reader runs, so
a long sweep followed by slow reads could hit the job timeout and cancel
the closing steps. The ceiling is 45 and a workflow test checks it covers
the gate's sizing plus the clear step's timeout. The clear-escalations
test for kept answers now also asserts exit 0, so a null reader answer is
kept as a verdict rather than caught as a per-PR error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton

Copy link
Copy Markdown
Member Author

Decision (owner, 2026-10-26): Q-CLEAR option B (strict). clear-escalations removes only lane-class needs-human labels on its own. A label whose recorded cause is person-class (security finding, trust gate, cost review, high-path, untrusted spec, repeated fail, too-old, files-capped) stays until a person removes it, so no HIGH/CRITICAL hold is narrowed. The change to B lands on this PR next; the security lane's two open CRITICAL threads are answered against it.

kyle-sexton and others added 2 commits October 26, 2026 09:28
Owner decision Q-CLEAR B: clear-escalations re-judges enable-auto-merge's
refusal at the same head only when the recorded cause is lane-class (every
code on LANE_CAUSES). A person-class recorded cause (issue link, security
finding, trust gate, cost, high path, untrusted spec, repeated fail, unknown
codes) is not read and not lifted, even when the reader finds nothing
refusing; a person removes the label, or head-moved clears it on a push.

enable-auto-merge's person refusal wording goes back to main's ("fix the
reason, then remove the label"): every label it sets is person-class, so the
same-head auto-removal promise no longer holds for any of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton kyle-sexton removed the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 26, 2026
@kyle-sexton
kyle-sexton enabled auto-merge October 26, 2026 14:09
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 2165a72 Oct 26, 2026
80 of 83 checks passed
@kyle-sexton
kyle-sexton deleted the fix/clear-native-escalations branch October 26, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant