Skip to content

Key checks by content, not by push — a base update re-runs everything it cannot affect #15967

Description

@mrveiss

Problem

A check re-runs when a push happens, not when its inputs change. Three cases pay for this today:

Case What actually changed What re-runs
Base update (auto-update-pr-branches merges base in) base delta only — the PR's own diff is untouched the whole applicable suite
Docs-only follow-up commit markdown the backend suite, if the PR ever touched backend paths
Any push after a green run one file every check the PR's cumulative diff selects

The first is the measurable one. Over 24 hours: 29 auto-update-pr-branches runs, and base-merge commits actually landed on the open PRs — #15954: 3, #15944: 2, #15959: 1, #15939: 1, #15962: 0. Seven refreshes, each restarting the applicable suite, at an 8.9-minute critical path — roughly an hour of critical path, and far more in parallel job time, for pushes that changed none of the PR's own code.

Six of seven open PRs are behind base as of filing, so this is the queue's steady state, not an edge case.

Why the existing filters do not cover it

dorny/paths-filter on a pull_request event compares the PR head against its base, so it selects on the PR diff. A base merge does not change the PR diff — it adds base's commits to the tree. The filter therefore selects exactly the same checks as before and every one of them re-runs.

Nothing currently looks at the base delta (old_base..new_base), which is the only thing that changed.

Scope

Key each check by the content it depends on, rather than by the event that triggered it:

key = <check name> + tree-hash(paths named by .github/filters/<check>-paths.yml)

An unchanged key means an unchanged verdict, and the prior conclusion is republished instead of recomputed. This subsumes all three cases in the table above with one mechanism.

Both halves already exist and should be reused rather than rebuilt:

  • .github/filters/*.yml already define, per check, the path set it depends on. They are documented as the single source of truth for exactly this question — see the header of code-quality-paths.yml.
  • The *-required-context.yml shims already publish a required context without running its job. Republishing a retained verdict is the same operation with a different reason string.

The limit this trades against — decide with it in view

Path-disjointness is not semantic independence. A PR changes f; base changes a caller of f under a different filter. The two are textually disjoint and behaviourally coupled, and a reused green is then wrong.

This hole already exists — every path filter in the repo assumes a check is a pure function of its filtered paths. Content-keying does not create the assumption, it leans on it harder, and base moving 14-40 commits/day makes the coupling likelier to bite than it is on the PR side. That is a deliberate trade and belongs in the decision, not in the small print.

A reused verdict must say so

A retained green and a fresh green must not look identical. This repository has five separate mechanisms whose failure mode is a verdict indistinguishable from its opposite — #15956, #15961, #15965, #15966 and the vacuous test in #15964 — and shipping a sixth would be a poor trade for saved minutes. The republished context must state that it was reused and name the run it came from, so "checked" and "assumed unchanged" are distinguishable in the artifact a human reads.

Relationship to #15937

The merge queue removes the base-update case entirely: PRs stop being refreshed speculatively, and one suite runs at merge against the base the change actually lands on. Those seven refreshes become zero. Do #15937 first; this issue keeps earning afterwards because the docs-only and incremental-push cases survive it.

Acceptance criteria

  • A check whose filtered paths are unchanged since its last green run republishes that verdict instead of re-running
  • The key is derived from the paths in .github/filters/*.yml, read at runtime — not a second inlined copy of the path set
  • Base updates specifically: a base merge that touches none of a check's paths does not re-run it — test with a PR whose diff and base delta are disjoint
  • A republished context states that it was reused and names the run whose conclusion it carries
  • Reuse is disabled for any check whose inputs are not path-expressible; list which checks those are and why
  • A required context can never be republished from a run against a different PR head diff — only the base may have moved
  • The semantic-coupling limit is documented where the filters are defined, so the next person to widen a filter knows what depends on it

Implementation Order

Wave: 3
Depends on: #15937
Umbrella: #15932

Activity

  1. mrveiss commented on Sep 7, 2026

    @mrveiss
    OwnerAuthor

    Sharpening one acceptance criterion, from a case found in the queue rather than in this issue's own scope.

    repo_tests/requirements_pin_parity_test.py carries three hard-coded version literals of identical shape:

    Same shape, opposite correct verdict. The only thing separating them is a docstring recording a decision — and a sweep staged an edit to all three, stopped only by reading it.

    This is a different failure from the one this issue currently describes, and stronger. Everything else logged today is output that fails to distinguish success from failure. This is code whose shape does not distinguish a defect from a deliberate constraint, and the record of the decision is the only thing that does.

    That matters here specifically, because content-keyed reuse decides not to run something and then reports a verdict it did not compute. A skip that says "unchanged, not run" is exactly the artifact above: correct and indistinguishable from a skip that should have run.

    Amended acceptance criterion — replacing the weaker "a republished context states that it was reused and names the run whose conclusion it carries":

    • A reused or skipped verdict cites the decision that made it safe, not merely the fact that it was skipped — the filter entry or recorded rationale that establishes this check is a pure function of those paths. A reader must be able to tell a sound reuse from an unsound one without re-deriving the reasoning.

    And an addition:

    • Where a path set is deliberately narrow or deliberately broad, that decision is recorded next to it, so a later sweep "simplifying" it fails loudly rather than silently widening the reuse window.

    Credit: found and diagnosed by Open issues scope and solution order while clearing the shard-8 failures; the near-miss on :152/:156 is the half that makes the case.

  2. mrveiss commented on Sep 8, 2026

    @mrveiss
    OwnerAuthor

    Sharpening the value argument, from a cost model that came out of an unrelated docs placement tonight.

    This issue has been justified in suites avoided. That is the wrong unit, and the right one changes what the issue is worth.

    The cost of a push is not one suite — it is whatever verdict the push destroys.

    • A push that restarts a run which had not finished: cost zero. There was no verdict; nothing was lost. The compute was already going to be spent or cancelled.
    • A push that restarts a run which had finished: cost one verdict. Something knowable was discarded, and the next run has to re-derive it.

    Two consequences that were not obvious before:

    1. Mid-run is the cheapest moment to push, not the worst. Both the framing on Auto-update starves long-running PRs: every merge restarts their CI, so the busiest nights are the ones nothing old lands #16015 and my own reading here assumed the opposite — that landing something into a running suite was the expensive case. It is the free one. The expensive case is pushing onto a PR that has just gone green.
    2. It reframes what this issue buys. Content-keyed reuse does not avoid suites; suites will run regardless. It preserves verdicts across pushes that cannot have changed the answer. A base merge that touches none of a check's paths currently destroys a completed verdict and pays to re-derive an identical one — that is the whole loss, and it is measurable in verdicts rather than in minutes.

    The measured backdrop from #16015 stands and reads differently under this unit: #15972 took 9 auto-update merges across 10 commits, #15977 8 of 9, #15978 7 of 8 — 28 restarts, ~670 check runs, zero completed suites. Zero completed is the point. Those 28 restarts destroyed almost no verdicts, because almost none had been reached; the harm was that no verdict was ever reachable, which is a starvation problem rather than a waste problem. They are different failures and need different fixes — this issue addresses the second, #15937 addresses the first.

    Suggested addition to the acceptance criteria:

    • The value of a reuse is stated in verdicts preserved, not suites avoided, and the measurement distinguishes a restart that destroyed a completed verdict from one that destroyed nothing

    Credit: the verdict-versus-suite distinction is 46's, arrived at while deciding when to place a docs file on a mid-run PR.

  3. added this to the v0.10.0 milestone on Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions