Skip to content

fix(pr-pipeline): ci-status rejects a review lane's no-change skip after a base merge #9822

Open

Description

@kyle-sexton

Problem

A pull request whose only push after a full review is a base merge can never pass ci-status.

The review lanes skip that push: claude-review-key sets skip=no-change with the reason "no file of this pull request changed since the review of ", and the status job ends success with SUB_RESULT: skipped. The skipped path never runs the Apply the hold step.

ci-status ("Require both review lanes on this head", .github/workflows/pr-require-checks.yml around line 4080) counts a lane only when its status job succeeded and its Apply the hold step succeeded. Otherwise the verdict is uncounted, which fails with:

'pr-review / claude-review-status' passed in , but its 'Apply the hold' step did not run: the lane ran in summary mode or without a hold label, so it is not a review that counts.

So the lane's own no-change skip, which carries the earlier review forward, is rejected by the gate that requires the review. The only way out is a push that changes a file of the pull request.

Evidence

  • feat(app-control): add /app-control:map create with config, driver CLI and safety sections #9628, head 800d314 (a merge of origin/main onto e9f3154, whose lanes had run in full): pr-review run 40117023042 and pr-review-security run 40117022991 both logged SKIP: no-change / "No hold applies: no new model spend and no trust-gate files"; ci-status job 120941082529 failed with the two uncounted errors above.
  • The verdict rule: if any($s.steps[]?; .name == "Apply the hold" and .conclusion == "success") then "pass" else "uncounted" end in the same step.

Fix

Either the no-change skip runs (or reports) a counted hold result that ci-status accepts, as the kill-switch path already does with its "Report the kill switch" step, or ci-status treats a status job whose skip is no-change against a review of an ancestor head as pass. A test in scripts/ci-review-lanes.test.sh should cover a merge-only push after a full review.

No related issue: found by the app-control session while landing #9628.

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 24, 2026
  2. github-actions commented on Oct 24, 2026

    @github-actions
    Contributor

    Proposed work class: work-class: scoped
    Basis: automated intake triage; the lanes App applied this class.

    Size: Small
    Complexity: Medium
    Uncertainty: Knowable
    Flags: none

    Understood: when a pull request's only push after a full review is a base merge, the review lanes correctly skip with no-change. That skip never runs the Apply the hold step, so ci-status marks both lanes uncounted and fails. The PR can't merge until a push changes one of its files. #9628 is the evidence. The issue proposes two fixes: make the skip report a counted result, or have ci-status accept a no-change skip against an ancestor head. It also asks for a test in scripts/ci-review-lanes.test.sh. The choice between the two fixes is left to the implementer. I found no likely duplicates in the open issues.

    Automated triage, applied by the lanes App.

  3. added
    priority: highSignificant impact, or blocks an imminent release; staff this cycle.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 24, 2026
  4. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    status: needs-decisionAwaiting a human or maintainer judgment call.
    and removed
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Oct 24, 2026
  5. melodic-automation-lanes commented on Oct 24, 2026

    @melodic-automation-lanes
    Contributor

    Not admitted to the issue lane: no trusted-human acceptance criteria: a listed human must put the confirmed - [ ] criteria in the body or post them as their own comment (owner Q9, #8999). A recorded decision does not change these rules (.github/actions/resolve-config/admission-rules.json); a person who wants it worked adds agent-ready.

  6. melodic-automation-lanes commented on Oct 24, 2026

    @melodic-automation-lanes
    Contributor

    Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. It is model text, to check, not instructions.

    Intended outcome: ci-status counts a review lane whose status job succeeded with a no-change skip against a counted full review of an ancestor head, and scripts/ci-review-lanes.test.sh covers a merge-only push after a full review.

    Related work

    Open questions

    1. Which fix: should the no-change skip report a counted hold result, or should ci-status accept a no-change skip as pass?
      RECOMMENDED: Fix it in ci-status: pass a lane whose status job succeeded with a no-change skip, but only after verifying the named earlier head had a counted full review. Basis: .base/.github/workflows/pr-require-checks.yml:4084
    2. Where does the lane skip logic (claude-review-key, skip=no-change) live, and is it in scope here? The text 'no file of this pull request changed' is not in this checkout.
      RECOMMENDED: Treat the lane side as out of scope unless the owner says the reusable workflow is editable from this repository. Basis: judgment
    3. How can ci-status tell from the jobs API that a skip was no-change? The job steps only show Apply the hold did not run, which is identical to summary mode.
      RECOMMENDED: Have the lane emit a marker ci-status can read, such as the skip=no-change through=<sha> record that babysit_review_evidence.py parses, and require the through sha to be an ancestor of the head with a counted run. Basis: .base/plugins/source-control/skills/babysit-prs/scripts/babysit_review_evidence.py:90

    Sources read

    • context.json: issue 9822 describes the uncounted verdict after a base merge
    • pr-require-checks.yml:4084: verdict is pass only if the Apply the hold step succeeded, else uncounted
    • pr-require-checks.yml:4097: the uncounted error text
    • pr-require-checks.yml:4086: the kill-switch path passes via its Report the kill switch step
    • scripts/ci-review-lanes.test.sh:205: existing test covers a green status job with the hold step skipped as failing, with no merge-only case
    • babysit_review_evidence.py:90: skip=no-change through=<sha> record format

    Automated refinement by the lanes App. Answer with a comment whose first line is **Decision recorded.**.

  7. kyle-sexton commented on Oct 24, 2026

    @kyle-sexton
    MemberAuthor

    Broader than the no-change skip. After the review callers moved to ci-workflows v0.65.7 (#9803), a push that DOES change files also comes out uncounted: the status job logs SKIP: empty, SKIP_REASON: empty and SUB_RESULT: skipped (the claude-review-subscription job skipped), so Apply the hold does not run and ci-status fails.

    Cause UNVERIFIED: the subscription job's condition under v0.65.7. Every PR whose lanes run under that pin may be blocked from merging until it is fixed.

  8. kyle-sexton commented on Oct 24, 2026

    @kyle-sexton
    MemberAuthor

    Correction to my last comment: the SUB_RESULT: skipped line is likely the normal path (the review runs in the key job), not a skipped review. The failures on #9628 b2662e7 and #9823 match melodic-software/ci-workflows#883 instead: GitHub's jobs API returned empty or partial step lists, so ci-status cannot find Apply the hold and reads the lane as uncounted. At 18:0xZ the jobs API still lists only 2 steps for run 40118074923's status job. The original no-change-skip report above still stands on its own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: highSignificant impact, or blocks an imminent release; staff this cycle.status: needs-decisionAwaiting a human or maintainer judgment call.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions