Skip to content

ci: queue the dev push runs instead of cancelling the waiting one - #734

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
agents/investigate-multiple-merges-velocity
Oct 21, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
agents/investigate-multiple-merges-velocity

Conversation

@ppenna

Copy link
Copy Markdown
Contributor

Part of #430.

Summary

Pushes to dev share one concurrency group (ci-CI-refs/heads/dev). With the default queue mode, a group keeps one running and one waiting run, and each newer push cancels the run that waits. When merges land faster than a run finishes, every merge but the last loses its run. This PR lets dev push runs queue in order (queue: max, up to 100 waiting) while pull requests keep cancelling superseded runs exactly as before.

Why

From October 4 to 21, 12 of the 199 dev push runs were cancelled before any job started, 9 of them on October 20 and 21. In the worst burst, #637, #635, #636, #633, and #634 merged within four minutes on October 20 and only #650's run executed. Each lost run means:

  • No development release for that commit (nvx.py download falls back to an earlier first-parent release).
  • No performance history rows for that commit.
  • A coverage gap: the detector diffs a push against github.event.before, so the surviving run classifies only its own merge, and can skip a suite that only a cancelled merge's files selected.
The 12 cancelled dev push runs (UTC)
Run Merge Created Waited, then cancelled
38296982838 #498 Oct 12 19:20 0.6 min
38297044799 #501 Oct 12 19:20 0.6 min
38297112053 #508 Oct 12 19:21 6.2 min
39425777328 #578 Oct 20 12:53 0.4 min
39436795942 #631 Oct 20 14:10 20.3 min
39482826940 #637 Oct 20 19:21 0.4 min
39482877385 #635 Oct 20 19:22 0.4 min
39482936162 #636 Oct 20 19:22 0.5 min
39483006325 #633 Oct 20 19:23 2.4 min
39483354020 #634 Oct 20 19:25 0.6 min
39525219681 #667 Oct 21 01:05 40.4 min
39529157695 #640 Oct 21 01:45 2.0 min

None of them started a job, so every one was a waiting run replaced by a newer push.

Merging itself is not blocked: dev has no merge queue and no up-to-date requirement. Development release publication and performance persistence already tolerate runs finishing in any order: each release is tagged by its commit, and persistence appends to the latest dev and retries a rejected push.

Options

Both options were replayed against the 199 observed dev pushes, using their measured durations and the median duration for the cancelled ones.

Option dev runs cancelled Start delay of a dev run Runner load
A. FIFO queue for dev pushes (this PR) none p50 0, p90 50 min, max ≈ 3 h (Oct 20) as today, one dev run at a time; +6% runs (those that used to be cancelled)
B. One concurrency group per dev push none none up to 7 overlapping dev runs at peak, competing with pull-request runs and the single benchmark runner per backend

A keeps pull-request turnaround, which gates merges, where it is. The lag only defers the post-merge release and baseline update.

Why the queue mode is an expression

  • ci.yml sets cancel-in-progress: ${{ github.event_name == 'pull_request' }}. GitHub rejects queue: max together with cancel-in-progress: true (docs), and Add concurrency queue support actions/languageservices#355 notes the combination is "also blocked at runtime". A static queue: max would therefore be rejected for every pull-request run.
  • The workflow schema types queue as single | max. Like cancel-in-progress, a concurrency property whose value is an expression is checked when the run is created, after the server evaluates it.
  • Public workflows already use this pattern:
    • reactive/data-client (benchmark.yml) uses the same pair: cancel-in-progress: ${{ github.event_name == 'pull_request' }} with queue: ${{ github.event_name == 'push' && 'max' || 'single' }}.
    • SonarSource/sonar-scanner-msbuild and chrisbanes/haze make the same split by branch.
    • In chrisbanes/haze on October 17, three main pushes queued and ran in order: 39071141458, then 39073679265, then 39073955706. The third was created while the second was still waiting, which the default mode would have cancelled. Its pull-request runs still cancel superseded heads.

Changes

  • .github/workflows/ci.yml: adds queue: ${{ github.event_name == 'pull_request' && 'single' || 'max' }} next to the existing cancel-in-progress, with a comment on why it is an expression. Scheduled and manually dispatched full runs keep their own per-run group.
  • scripts/test_nvx_tools.py: adds test_ci_dev_pushes_queue_while_pull_requests_cancel. It pins both settings and rejects a static queue: max.
  • doc/ci.md: adds a Run concurrency section covering the groups, what the single waiting run lost, the constraint, and the replayed waits.
  • .github/copilot-instructions.md, .github/skills/nvx-ci-investigation/SKILL.md: a cancelled dev push run is no longer treated as an expected supersession.

Validation

  • python -m unittest scripts.test_nvx_tools.CiConfigurationTests: 67 tests OK (3 skipped) on the rebased head.
  • The new test fails when queue is a static max, a quoted 'max', or missing.
  • ruff check, ruff format --check, pyright (Linux and Windows platforms), and scripts/check_markdown_links.py pass.
  • The full validate-nvx unittest set ran locally on Windows (1,090 tests). The only failures are 18 test_setup_azure subtests, which fail identically on the unmodified base on that machine.

Post-merge verification

  • This PR's run exercises the single branch together with cancel-in-progress: true.
  • The merge's dev push run exercises the max branch. It must start its jobs rather than fail workflow validation.
  • The next burst of merges should leave later dev runs pending instead of cancelled.
  • Rollback: delete the queue line.

Pushes to dev share one concurrency group, which keeps one running and
one waiting run, and a newer push cancels the run that waits. When
merges land faster than a run finishes, each merge but the last loses
its run: 12 of the 199 dev pushes from October 4 to 21 were cancelled
before any job started, 9 of them on October 20 and 21, and five merged
within four minutes on October 20 left a single run. Those commits got
no development release and no performance results, and because the
detector compares a push with its previous commit, the run that
followed classified only its own changes and could skip a suite that a
cancelled merge needed.

Let up to 100 runs of the group wait and start in turn with queue: max
for every event but pull_request. GitHub rejects queue: max together
with cancel-in-progress: true, so the workflow derives both settings
from the event, and pull requests keep the single waiting run and
cancel the run of an older head as before. Scheduled and manually
dispatched full runs keep a group of their own. GitHub orders the
waiting runs by when they began to wait, without guaranteeing it, and
neither the development release, which is tagged by its commit, nor
performance persistence, which appends to the latest dev and retries a
rejected push, depends on that order.

A queued run waits for those ahead of it. Replaying the dev pushes of
October 4 to 21 one at a time, half would have started at once, one in
ten would have waited at least 50 minutes, and the longest wait, on
October 20, would have been about three hours. The runners still carry
one dev run at a time; the only added load is the runs that the group
used to cancel, about 6% of the dev pushes in that period.

The workflow schema accepts an expression for each concurrency
property, and public workflows already queue their default branch's
pushes while cancelling superseded pull-request runs the same way. A
CI configuration test pins both settings and rejects a static
queue: max, which would keep every pull-request run from starting.
doc/ci.md describes run concurrency, and the Copilot instructions and
the CI investigation skill no longer treat a cancelled dev push run as
an expected supersession.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 21, 2026 20:08

Copilot AI 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.

🟢 Approval recommended

The focused configuration change preserves pull-request behavior, has consistent tests and documentation, and has no identified blocking issues.

No open issues 🎉

What changed in this PR

Preserves CI coverage for successive dev merges by queueing push runs instead of replacing waiting runs, while retaining pull-request cancellation.

Changes:

  • Selects queue mode by event without changing concurrency groups.
  • Adds regression assertions for queue and cancellation settings.
  • Updates concurrency documentation and cancellation-triage guidance.
File Description
scripts/​test_nvx_tools.py Tests event-dependent concurrency settings.
doc/​ci.md Documents queue behavior and expected delays.
.github/​workflows/​ci.yml Enables queued pushes while preserving PR cancellation.
.github/​skills/​nvx-ci-investigation/​SKILL.md Distinguishes push cancellations from expected supersession.
.github/​copilot-instructions.md Updates repository concurrency guidance.

Review effort: Balanced

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.

2 participants