Skip to content

ci: route a pull request from its last green head when it only merged main - #14659

Merged
teamleaderleo merged 7 commits into
mainfrom
ci/delta-since-green
Sep 25, 2026
Merged

teamleaderleo merged 7 commits into
mainfrom
ci/delta-since-green

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Slice 2 of #14631 (delta CI). Slice 1 (catch-up job) is separate.

What changes

When a pull request's head H2 is its previous head H1 plus one or more merges of main, and at most one resolution commit on top, and H1's ci-status passed, the changes job diffs from H1 instead of from main. Routing then covers only what changed since green: main's delta and the resolution.

  • scripts/ci/delta_since_green.py finds H1:
    • Walks H2's first-parent chain after a commit-only fetch (--filter=tree:0).
    • Reads ci-status for the candidate commits in one GraphQL call with the workflow token. Only pull_request runs of .github/workflows/ci.yml count, matched by file path, not by name.
    • Binds the verdict to this pull request: one runs API call for the nearest judged commit keeps only runs whose pull_requests include this PR number with this base branch. A stacked PR on another base, or a run with no PR attached, says nothing.
    • Within one run the latest attempt wins, so a rerun can clear a flake. A red ci-status from any other run of this PR makes the head red.
    • H1 is the nearest commit with a verdict. That verdict must be success, and every commit between H1 and H2 must be a merge whose second parent is on main. Only H2 itself may be an ordinary commit.
  • CI policy edits keep the whole diff. If the PR's own diff touches .github/, scripts/ci/, tests/test_ci_* or tests/test-execution.toml, the delta is skipped. Otherwise the trusted-router switch, the stdlib-shadow guard and guard-test self-selection, which ci.yml decides by whether those files are in the diff, could lose them. Main's own policy edits still arrive in the delta and are classified as usual.
  • Never shrinks below the pull request's own needs. Every file the pull request changes against main must either differ since H1 or have been in its diff at H1 (merge-base(H1, main)..H1). A merge that kept the pull request's side of a file only main had edited fails open.
  • Fails open in every other case, and routing stays unchanged: not a pull_request event, a plain new commit, a new commit on a green merge, H1 red or without a verdict, a force push, a merge of a non-main branch, history too shallow (it deepens 3000 commits, commits only), a PR that edits CI policy, or an API error.
  • ci.yml:
    • A new delta step runs the base revision's copy of the selector, like the trusted router.
    • The step summary says which base was used and why, e.g. delta since green head abc1234567: 3 files (pull request diff: 12 files).
    • The detector sets BASE_SHA=$DELTA_BASE_SHA only when one was found. The changes job gains checks: read.
  • Kill switch: set the repository variable CI_DELTA_SINCE_GREEN to 0. The step runs for same-repository pull requests only: fork runs get no repository variables, so the switch could not reach them, and GitHub ties no PR to a fork run.

Because the step runs the base copy, this PR's own run does not exercise it. The first PR run after merge will.

Tests

  • tests/test_ci_delta_since_green.py (19 tests). Each builds a fixture origin repo and runs the selector from a depth-2 clone of GitHub's merge commit, as actions/checkout leaves it. Cases:
    • a clean merge of main
    • a merge plus a resolution commit
    • a normal new commit, which makes no API call
    • a new commit on a green merge
    • H1 red, and H1 missing a verdict
    • two merges in one push, and a second merge after a green first merge
    • a force push
    • a merge of a non-main branch
    • a merge that kept the head's side of a file only main had edited
    • a head that is not the tested merge's parent
    • verdict selection, fail-open main(), and the ci.yml wiring
  • Existing suites all pass: tests/test_ci_change_areas.py, test_ci_linux_guard_routing.py, test_ci_guard_workflow_structure.py, test_ci_workflow_guards_are_wired.py, test_ci_repo_variable_defaults.py, test_ci_fork_runner_routing.py, test_ci_cli_product_routing.py, test_ci_workflow_path_filter_parity.py and test_ci_test_execution_registry.py.
  • actionlint is clean.

Known gaps

  • Package lane still selects on the full diff. ci-macos.yml picks Swift packages from its own HEAD^1..HEAD diff on the Mac runner, not from the delta. When the delta routes the package lane, the lane still selects packages from the whole PR diff. That runs more than needed, never less; passing the delta list to that job is a follow-up.
  • The delta is diff(H1, merge commit), so if main moved again after H2, that movement is routed too (conservative).
  • Areas are carried forward at file level: a file proven at H1 is not re-routed. Interactions between main's changes and the pull request that stay outside the dependency closure are the risk [RFC] PR catch-up: auto-merge main, resolve generated conflicts, rerun only the delta #14631 accepts. Main's full suite still catches them after merge.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Routes CI diffing for a pull request whose new head only merged main: instead of diffing against main, the changes job diffs from the PR's last green head, so only main's delta plus any resolution commit are tested again.

  • scripts/ci/delta_since_green.py picks the green head from the head's first-parent chain using ci-status verdicts, applying only when every commit since is a merge of main (plus at most one resolution commit); everything else fails open to the usual diff.
  • Verdicts count only ci.yml runs (matched by file path) tied to this pull request number and base branch; forks, stacked PRs, and canceled or in-progress runs say nothing. The latest attempt in a run wins, and any red run makes the head red.
  • The selector never drops a file the PR changes: a file must differ since the green head or have been in its diff there.
  • Fetches use a partial-clone filter and retry without it when refused; history deepens only while the checkout is still shallow, and a fetch failing both ways fails open with git's stderr in the reason.
  • A PR that changed its base branch keeps its whole diff: the runs API fills in a run's base ref at query time, so a retargeted head's earlier runs would otherwise read as on the new base.
  • A PR whose own diff touches CI policy (.github/, scripts/ci/, tests/test_ci_*, the test registry) keeps its whole diff and never routes from a green head.
  • A new delta step in ci.yml runs the base revision's copy and logs the chosen base; set the CI_DELTA_SINCE_GREEN repository variable to 0 to disable. It defaults to on, and runs for same-repository PRs only.
  • ci-macos.yml over-selects packages on routed lanes but never under-selects.
  • Adds 19 tests covering merges, resolution commits, red or missing verdicts, force pushes, verdict binding, base-branch retargets, CI-policy edits, fetch fallbacks, and fail-open paths.

Written for commit 938fac2. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • CI Improvements
    • For eligible pull requests, CI can now use the most recent qualifying successful run on that pull request as its comparison base.
    • If the pull request history, successful-run results, or required information do not meet the selection criteria, CI uses its standard comparison instead. Errors while selecting a base also fall back to the standard comparison.
    • This behavior applies only to supported same-repository pull requests and can be disabled through CI configuration.

… main

RFC #14631, slice 2. When a pull request's new head is its previous
green head plus merges of main (and at most one resolution commit on
top), its own changes already passed there. The changes job now diffs
from that green head instead of from main, so only what changed since
green is routed.

scripts/ci/delta_since_green.py walks the head's first-parent chain
(commit-only fetch), reads the ci-status verdicts of the candidates in
one GraphQL call, checks each merge brought in a main commit, and
refuses when a file the pull request now changes was neither in its
diff at the green head nor changed since. Anything else fails open to
the usual diff. ci.yml runs the base revision's copy, writes the chosen
base and reason to the step summary, and the repository variable
CI_DELTA_SINCE_GREEN=0 turns it off.

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

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@blacksmith-sh

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 7 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: dd0a63e2-9540-471c-b8a5-3990e2326ab9

📥 Commits

Reviewing files that changed from the base of the PR and between 3e26626 and 938fac2.

📒 Files selected for processing (6)
  • .github/workflows/ci-guards.yml
  • .github/workflows/ci.yml
  • scripts/ci/delta_since_green.py
  • scripts/ci/workflow_guard_groups.py
  • tests/test-execution.toml
  • tests/test_ci_delta_since_green.py

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: b1f6d191-f583-487a-ac72-4d09df5938c2

📥 Commits

Reviewing files that changed from the base of the PR and between 4be951c and 3e26626.

📒 Files selected for processing (2)
  • scripts/ci/delta_since_green.py
  • tests/test_ci_delta_since_green.py

Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The CI workflow can select a validated green pull request head as the change detector’s diff base. The selector checks Git history, changed paths, and pull request CI verdicts. If it cannot select a base, the workflow uses the usual pull request diff.

Changes

CI diff base selection

Layer / File(s) Summary
History inspection and green-head selection
scripts/ci/delta_since_green.py, tests/test_ci_delta_since_green.py
Adds Git history inspection, routing criteria, and green-head selection. Tests cover eligible merge histories, rejected history shapes, and fetch failures.
Verdict lookup and base decision
scripts/ci/delta_since_green.py, tests/test_ci_delta_since_green.py
Binds successful CI verdicts to the requested pull request and base branch. Validates candidate history and changed-file coverage before writing a selected or empty base.
Workflow routing and guard coverage
.github/workflows/ci.yml, .github/workflows/ci-guards.yml, scripts/ci/workflow_guard_groups.py, tests/test-execution.toml, tests/test_ci_delta_since_green.py
Runs the selector for eligible same-repository pull requests and passes its base to change detection. Classifies the selector as CI routing policy and registers its tests in CI guard coverage.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant ChangesJob
  participant DeltaScript as delta_since_green.py
  participant Git
  participant GitHubAPI as GitHub API
  participant Detector
  ChangesJob->>DeltaScript: invoke with merge SHA and pull request details
  DeltaScript->>Git: inspect history and changed files
  DeltaScript->>GitHubAPI: retrieve pull request CI verdicts
  GitHubAPI-->>DeltaScript: return workflow run and check conclusions
  DeltaScript-->>ChangesJob: write selected or empty base SHA
  ChangesJob->>Detector: pass delta base for change detection
Loading

Merge Risk: ⚪ Minimal · up to 3e266

The selected diff covers changes since the validated green head. No actionable merge-blocking risk was identified.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 3e266

The narrower CI route has safeguards and falls back to the usual diff on many failures. Two boundaries still merit review: verdict lookups can omit results beyond an API page, and the new check-reading permission is available to other code run by the job.

Retained concerns

  • Medium · security · inferred: The green-head decision can treat a commit as green without considering every relevant run when check suites or workflow runs exceed the unpaginated API limits. If an omitted run was red, the resulting narrower diff could skip checks that the usual PR diff would select.
  • Medium · security · inferred: Check-reading authority is granted to the entire changes job, which later passes its token to a script executed from the PR checkout. The selector’s use of trusted base code does not confine the new permission to that selector.
Security review details

Security Blast Radius

  • inferred — An incorrect green verdict affects checks selected for one qualifying PR run. The added checks: read permission applies throughout that run’s changes job, rather than only during verdict lookup.

Security Findings and Attack Paths

  • inferred — If relevant runs exceed an API page and a red result is omitted while a green result remains visible, the selector can choose a narrower base despite its stated any-red-run rule. The repository evidence does not establish that this condition has occurred.
  • inferred — A contributor able to change the checkout’s pool-routing script could execute code in a later step that explicitly receives the job token, now with checks: read. This does not establish access to write permissions or secrets.

Trust Boundaries and Controls

  • observed — Verdict selection filters by workflow path and PR event, binds run IDs to the requested PR and base, rejects retargeted PRs, and falls back to the whole PR diff for CI-policy edits or uncovered files. Those controls do not establish completeness of capped API responses.

Resilience and Maintainability Implications

  • observed — Missing history, unsuitable merge shape, fetch errors, and selector exceptions leave the delta base empty. Disabling the delta step restores base selection but is not a rollback of its job permission.

Hardening Proposals

  • proposed — Paginate verdict sources or retain the whole PR diff whenever a relevant response may be incomplete.
  • proposed — Confine checks: read to an isolated trusted lookup job or step boundary, rather than sharing its token authority with later checkout code.
🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.81% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 74 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Cloud Persistent Session And Early Input ✅ Passed PASS: The PR changes only CI workflows, CI routing scripts, guard-group configuration, and CI tests. The authoritative diff contains no Cloud terminal creation, cmux-tui transport, manual renderer, Gh…
Cmux Swift Actor Isolation ✅ Passed The pull request changes only GitHub Actions YAML, Python CI scripts, and test configuration/Python tests. The authoritative diff contains no Swift or Swift UI production files, so the Swift actor iso…
Cmux Swift Blocking Runtime ✅ Passed The pull request changes only Python, YAML, and TOML files. It introduces no production Swift changes, so it does not introduce or expand Swift blocking or timing-based synchronization.
Cmux Browser Automation Off-Main ✅ Passed PASS: The pull request changes only CI workflows, CI selector code, guard-group routing, and CI tests. It does not change Sources/TerminalController.swift, ControlCommandExecutionPolicy.swift, or …
Cmux Expensive Synchronous Load ✅ Passed The pull request changes only CI workflow files, Python CI scripts, and test configuration/tests. The authoritative diff contains no Swift files or production Swift code, so it cannot introduce an exp…
Cmux Cache Substitution Correctness ✅ Passed PASS: The pull request changes only Python, YAML, and TOML files. It contains no production Swift, TypeScript, or JavaScript changes. Therefore, the cache-substitution correctness condition does not a…
Cmux No Hacky Sleeps ✅ Passed PASS. The diff adds no sleep, timer, polling loop, or wall-clock delay used for lifecycle synchronization. The workflow timeout-minutes: 3 is GitHub Actions orchestration, which the rule excludes. T…
Cmux Algorithmic Complexity ✅ Passed PASS. The pull request changes CI workflow shell and a Python CI selector, not production Swift, TypeScript, or JavaScript paths. The selector uses bounded first-parent history (MAX_CHAIN = 8), boun…
Cmux Swift Concurrency ✅ Passed The pull request changes only Python, YAML, and TOML files. The authoritative diff contains no Swift files, so it does not introduce or expand any Swift concurrency pattern.
Cmux Swift @Concurrent ✅ Passed PASS: The pull request changes only CI workflows, Python CI scripts, and test configuration. The authoritative diff contains no .swift files, Swift functions, or Swift call sites, so the `@concurren…
Cmux Swift Package Boundaries ✅ Passed The pull request changes only YAML, Python, and TOML files. The authoritative diff contains no Swift files or SwiftPM manifests, so the Swift package boundary check is not applicable.
Cmux Swiftpm Lockfiles ✅ Passed The pull request changes only CI workflows, CI scripts, guard-group configuration, and test registration/tests. It does not change any Package.swift, Package.resolved, .gitignore, Xcode project/…
Cmux Swift Logging ✅ Passed PASS: The pull request changes only CI workflow files, Python CI scripts/tests, and TOML. It adds or materially changes no Swift files or Swift logging statements, so the Swift logging rules do not ap…
Cmux User-Facing Error Privacy ✅ Passed PASS. The changed files are limited to GitHub Actions workflows, CI scripts, CI guard wiring, and tests. The new messages go to GitHub Actions output and the step summary for CI operators. No changed …
Cmux Full Internationalization ✅ Passed The PR changes only CI workflows, CI scripts, routing metadata, and tests. The authoritative diff contains no Swift, web UI, catalog, locale, changelog, or user-facing data files, and the added text i…
Cmux Swiftui State Layout ✅ Passed The pull request changes only CI workflows, Python CI scripts, and test configuration/tests. The authoritative diff contains no Swift or SwiftUI files and no SwiftUI state/layout changes. Therefore th…
Cmux Architecture Rethink ✅ Passed PASS: The pull request changes only CI workflows, Python CI scripts, workflow-group configuration, and tests. It introduces no Swift, SwiftUI, or AppKit changes, so the Swift architectural-rethink fai…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The pull request changes only CI scripts, workflow files, and tests. The authoritative diff contains no Swift changes and no standalone cmux-owned window code. The auxiliary-window close-shortcu…
Cmux Source Artifacts ✅ Passed All six changed paths are intentional CI configuration, source, test, or test-registry files. The added Python files are hand-written source and tests. No changed path is a scratch directory, generate…
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS: The pull request changes no Swift files. It has no file under a production Sources/ path, so the custom check does not apply.
Title check ✅ Passed The title clearly and concisely describes the primary change: routing pull requests from their last green head when the new head only merges main.
Description check ✅ Passed The description provides a detailed summary, implementation behavior, testing coverage, known gaps, and fail-open conditions. It does not include the template checklist, but the core required informat…
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

teamleaderleo and others added 5 commits September 25, 2026 13:34
…e full diff

Review of #14659:

- A pull request whose own diff touches CI policy (.github/, scripts/ci/,
  tests/test_ci_*, the test registry) now keeps its whole diff. The
  delta would otherwise drop the router, stdlib-shadow and guard-test
  inputs that ci.yml classifies by their presence in the diff.
- The green head's verdict counts only ci.yml runs (matched by file path)
  that the runs API ties to this pull request number and base branch; a
  stacked pull request on another base or a fork run says nothing.
- Within one run the latest attempt wins; a red ci-status from any other
  run of this pull request makes the head red.
- The step runs for same-repository pull requests only, where the
  CI_DELTA_SINCE_GREEN switch can reach it. The workflow test now
  evaluates the step's condition for forks, other events and the switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-review of #14659: the runs API fills a run's pull_requests[].base.ref
in at query time, so after a retarget (feature-x to main) the green
head's old run would read as a run on main. The verdict query now asks
for the pull request's BASE_REF_CHANGED_EVENT timeline items and skips
the delta on any, or when the field cannot be read. It reads
filteredCount and nodes: totalCount counts the whole timeline whatever
itemTypes says (7 on this pull request, which was never retargeted).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#14659's guard run failed on the Linux runners (git 2.52): the --deepen
fetch exited 128 in the two-merge fixtures. The chain fetch already
completes a short history, and --deepen on a complete repository is what
that git refuses. The selector now deepens only while the checkout is
still shallow, retries any filtered fetch without its filter, and puts
the fallback (with git's stderr) in the step summary. A fetch that fails
both ways skips the delta with git's stderr in the reason.

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

This comment has been minimized.

@teamleaderleo
teamleaderleo merged commit f65d114 into main Sep 25, 2026
50 of 51 checks passed
@teamleaderleo
teamleaderleo deleted the ci/delta-since-green branch September 25, 2026 21:26
@github-actions

Copy link
Copy Markdown
Contributor

Merge receipt for 938fac2bab, merged 2026-09-25 21:26:42 UTC

  • Not verified at merge: receipt-contract (failure)
  • Verified: ci-status, Web complexity, web-validation, Fast static checks, GhosttyKit release check, guards (18), host-tests, Testbox broker trust boundary, tests, transport
  • Skipped by policy: Claude wrapper regressions, linux-preflight, macos, macOS admission gate, remote-daemon, suite-coverage, web, web-build, web-database-tests, web-tests
  • Full suite: runs on main after merge.

teamleaderleo added a commit that referenced this pull request Sep 27, 2026
…#14987)

Delta CI (#14659) routes diff(last green head, merge), which carries
everything main gained since the pull request last synced. When main
moved further than the pull request, that routes more than the pull
request's own diff: #14961 routed 40 files for a 15-file diff and #14960
43 for 10, picked about 130 unrelated unit test classes, and failed
suite-coverage on main's cmuxUITests/ edits from #14966.

Take the delta only when it is no larger than the pull request diff and
carries no cmuxUITests/ edit the pull request did not make. Otherwise
route the usual pull request diff, as every other skip does.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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