Skip to content

fix(pr-pipeline): hold native lane PRs on marketplace and cost paths - #9488

Merged
kyle-sexton merged 5 commits into
mainfrom
fix/9449-native-lane-holds
Oct 23, 2026
Merged

kyle-sexton merged 5 commits into
mainfrom
fix/9449-native-lane-holds

Conversation

@kyle-sexton

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

Copy link
Copy Markdown
Member

Closes #9449

Summary

Under merge.lane-prs: native, only ci-status and enable-auto-merge.mjs hold a lanes App pull request, and both read HOLD_PATH_FLOOR in merge-holds.mjs. Two gaps meant a lane PR could merge where the pr-merge gate would have held it:

  • .claude-plugin/marketplace.json, which the gate scores HIGH (babysit_risk.is_manifest_path), was not a hold path.
  • The gate's cost paths (babysit_risk.COST_PATHS) were not held at all.

Fix

  • Marketplace. .claude-plugin/marketplace.json joins HOLD_PATH_FLOOR as a plain path. Lane PRs rarely edit it, so the whole file is held and no content diff is read. It clears the way every floor path clears: a trusted person approves the head.
  • Cost paths. merge-holds.mjs gains COST_PATHS and costHold, kept apart from the floor so an approval cannot lift them. Only the owner's cost decision lifts the hold. costDecision reads it by the gate's cost_decision rule: an unedited comment opening **Decision recorded.** Cost:, by a User and not posted through an App, linked from the body outside HTML comments. The comment sits on the PR itself, or on its one closing issue in this repository and posted after the PR was created. Matching is case-folded, and a glob covers whatever sits under it, as glob_match does.
    • ci-status runs this inside merge-holds.mjs. It reads only when a cost path changed and the body links a comment, and it uses the step's existing GH_TOKEN, so the workflow step's shell is unchanged.
    • enable-auto-merge.mjs runs the same costHold and costDecision in decide and its read step. It reads the owner list from .base/.github/standards/trusted-actors/trusted-actors.json, or TRUSTED_ACTORS when set.
  • Owner. "Owner" means a person on the trusted-actor list. ci-status's existing Clear a cost-only review hold the owner decided step uses the same rule, and Python's cost_decision asks for admin permission instead. This choice needs no permission read with either token. It also drops Python's agent-identity and self-login exclusions: those accounts are Apps, which the User check already refuses.
  • Issue wording. The issue's done-when says the cost hold lasts "until a trusted person approves the head". The decided scope overrides that: the cost hold clears only on the owner's cost decision, the rule the gate already applied, and an approval does not lift it.
  • Parity. test_merge_holds_sync.py runs the module under node. It checks that COST_PATHS equals babysit_risk.COST_PATHS, that sample paths (case variants, dot directories, a path under a glob) match the same way in both, and that every path is_manifest_path takes is a hold path.
  • Docs. The pr-pipeline README no longer says "every plugin manifest". It now lists the manifests the floor holds and describes the cost hold.

merge.lane-prs is unchanged.

Verification

  • node --test in .github/scripts/pr-merge: 252 pass, 0 fail.
  • node --test merge-authority.test.mjs in .github/actions/resolve-config: 26 pass.
  • pytest tests/test_merge_holds_sync.py tests/test_verify_rules_sync.py tests/test_babysit_merge_cost.py tests/test_babysit_risk_cost.py: 60 passed, 222 subtests.
  • scripts/ci-lanes-pr-hold.test.sh (100 pass), ci-hold-lifted-by.test.sh (33), ci-hold-merge-group.test.sh (22).
  • Fails before the fix, checked by reverting each part on its own:
    • With marketplace dropped from the floor, the enable-auto-merge decide test and the ci-status test fail.
    • With costHold made inert, the decide/main test, the costHold test and the ci-status cost test fail.
    • With the under-glob coverage removed, the sync test fails.
  • The ci-status test also shows that a trusted approval does not lift a cost hold, that a cited decision does, and that an edited decision does not.
  • costDecision negatives mirror test_babysit_merge_cost.py.
  • Lint: scripts/run-ruff.sh check and format, typos, actionlint and markdownlint are clean on the changed files.
  • Security review (review:security-reviewer) found nothing at MEDIUM or above. Both LOW findings are fixed in the second commit:
    • *.tf-style globs now match a dot-led file name such as .tf, as glob_match does. The sync test covers this.
    • enable-auto-merge escapes the hold reasons it logs, so a file name cannot start a workflow command. A test covers this.
  • Three INFO items from the review are left for later and noted here:
    • A cost decision covers its issue or PR, not a specific head. That is the same as the gate's rule.
    • Python's other cost reasons are not mirrored: Pulumi project directories, and workflow and pipeline lines. Workflows and pipeline lines already fall under hold paths, and there are no Pulumi projects in the tree yet.
    • casefold and backslash path forms differ between the two implementations.
  • Ready-step reviews (fresh context, over the PR diff):
    • Code review: the cost-decision rule had no check that the JS copy matches the gate's. test_merge_holds_sync.py now runs one table of 14 decision cases through both cost_decision and costDecision; it fails when costDecision stops checking edits. Its docstring now names only the repository's pipeline config. Left as is: fetchGitHub makes one attempt per read, which fails closed.
    • Security review: nothing at MEDIUM or above. Left as is, each the same in the gate or not reachable in this repository today: no cap on decision links in the body (the gate and the shell step loop the same way); files inside a Pulumi project directory and Terraform *.tf.json/*.tfvars.json are not cost paths (no IaC is tracked); a decision is not tied to the head; owner comments posted through the owner's own token count as the owner's.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 4 commits October 23, 2026 16:29
ci-status and enable-auto-merge now hold a lanes App pull request that
changes the root marketplace manifest (a hold path, lifted by a trusted
approval of the head) or a merge-gate cost path. The cost hold lifts only
on the owner's unedited `**Decision recorded.** Cost:` comment linked from
the body, read as the gate's cost_decision reads it, with the owner a
person on the trusted-actor list as ci-status's cost-only review step
names one. A Python test keeps the JS cost globs and matches in step with
babysit_risk.COST_PATHS and is_manifest_path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Match `*.tf`-style cost globs against a segment that opens with ".", as
babysit_risk.glob_match does, and escape the hold reason enable-auto-merge
logs so a changed file's name cannot start a workflow command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The native cost hold's costDecision copies babysit_merge_policy.cost_decision
by hand. Run one fixture table through both so a change to either rule fails
the sync test instead of letting native auto-merge clear a comment the gate
rejects. Also narrow the docstring to the repository's pipeline config, the
one the floor holds.

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

chatgpt-codex-connector Bot commented Oct 23, 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-23T20:48:12.691809Z e43a370 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.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

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

Incremental review of .github/scripts/pr-merge/enable-auto-merge.test.mjs since e43a370: no findings.

The change is comment-only, in two places. It rewords the cost-decision explanation near line 97, and the note about pull request 42 in the cost-path test near line 419. The new wording says pull request 40's closing issue holds the owner's comment, and that each pull request closes its own number plus 1000. That matches the previous wording, where 40 maps to issue 1040 and 42 to issue 1042. Code, assertions and test behaviour are unchanged.

I did not run the tests. Nothing here touches a hold class (cost, trust-gate or critical-security), and no manifest items apply. The wrapper supplied no closing-issue criteria that this change could affect.

@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 e43a37002778b14bb24908051ebf4cc71ad5a470; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

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

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

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

Incremental security review of .github/scripts/pr-merge/enable-auto-merge.test.mjs (since e43a370). The change only rewords two test comments. It drops the hard-coded issue numbers (1040, 1042) and describes the same relationships in words. No code, assertion, or test logic changed, and nothing touches a plugin manifest, workflow, or instruction surface. I found no security issues.

critical-findings: 0

…ments

comment-hygiene refuses issue references in code comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit be7e121 Oct 23, 2026
43 checks passed
@kyle-sexton
kyle-sexton deleted the fix/9449-native-lane-holds branch October 23, 2026 21:44
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.

fix(pr-pipeline): hold lane PRs on marketplace sources and cost paths under native auto-merge

1 participant