Run the whole repo in one concurrency lane #45

Merged
geusebio merged 1 commit from ci/serialize-runs into main 2026-08-08 14:30:00 +00:00
Owner

The concurrency group keyed on the ref, which serialises nothing: different branches get different groups, so branch runs always executed concurrently. They share one Swarm manager and one disk, so they did not merely contend -- they took each other out, and always after the expensive part was already done:

run failure
317 main all ~40 images built (690s), then died bringing containers up: Bind for 0.0.0.0:3181 failed: port is already allocated -- another run held the test-healthchecks.yml host ports
314 branch frontend grpc server closed unexpectedly, with four runs in flight

CLAUDE.md already records the same shape for no space left on device and Internal: unknown blob sha256:... (buildkit GC evicting blobs out from under an in-flight export). None of it says anything about the change under test, and each one costs a full re-run -- so a large share of the time spent waiting for this pipeline was spent waiting for it twice.

So: one lane for the workflow. Runs queue instead of overlapping.

Why not cancel-in-progress

It stays false. Cancelling a main run mid-publish leaves some targets pushed and some not, which is how the registry ended up holding an incomplete set of build-stamped tags -- runs 265 and 270, both killed 7s into pushing layers.

Queued runs still do not pile up: while one run holds the lane, only the newest queued run survives and older pending ones are dropped. The trade is that pushing again no longer kills a run that has already started; it queues behind it.

Note on the ports

The 26 published host ports in test-healthchecks.yml are still fixed and still global to the daemon -- #43 gave each run its own compose project, but not its own ports. Serialising is what stops them colliding. Switching them to ephemeral ("80") would make collisions structurally impossible and was the alternative considered here; it was not taken because it would cost the stable localhost:3084-style ports used for local debugging.

Anything else sharing that daemon can still collide with us; the lane only covers this workflow.

Validated with act -n (both jobs).

🤖 Generated with Claude Code

The concurrency group keyed on the ref, which serialises nothing: different branches get different groups, so branch runs always executed concurrently. They share one Swarm manager and one disk, so they did not merely contend -- they took each other out, and always *after* the expensive part was already done: | run | | failure | |---|---|---| | 317 | main | all ~40 images built (690s), then died bringing containers up: `Bind for 0.0.0.0:3181 failed: port is already allocated` -- another run held the `test-healthchecks.yml` host ports | | 314 | branch | `frontend grpc server closed unexpectedly`, with four runs in flight | CLAUDE.md already records the same shape for `no space left on device` and `Internal: unknown blob sha256:...` (buildkit GC evicting blobs out from under an in-flight export). None of it says anything about the change under test, and **each one costs a full re-run** -- so a large share of the time spent waiting for this pipeline was spent waiting for it twice. So: one lane for the workflow. Runs queue instead of overlapping. ### Why not cancel-in-progress It stays `false`. Cancelling a main run mid-publish leaves some targets pushed and some not, which is how the registry ended up holding an incomplete set of build-stamped tags -- runs 265 and 270, both killed 7s into `pushing layers`. Queued runs still do not pile up: while one run holds the lane, only the newest queued run survives and older pending ones are dropped. **The trade** is that pushing again no longer kills a run that has already started; it queues behind it. ### Note on the ports The 26 published host ports in `test-healthchecks.yml` are still fixed and still global to the daemon -- #43 gave each run its own compose *project*, but not its own ports. Serialising is what stops them colliding. Switching them to ephemeral (`"80"`) would make collisions structurally impossible and was the alternative considered here; it was not taken because it would cost the stable `localhost:3084`-style ports used for local debugging. Anything else sharing that daemon can still collide with us; the lane only covers this workflow. Validated with `act -n` (both jobs). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ci: run the whole repo in one concurrency lane
All checks were successful
Build / Run container tests (push) Successful in 13m13s
Build / Build (push) Successful in 29m52s
4e05b462b0
The concurrency group keyed on the ref, which serialises nothing:
different branches get different groups, so branch runs always executed
concurrently. They share one Swarm manager and one disk, so they did not
merely contend, they took each other out -- and always after the
expensive part was already done:

  run 317  main    all ~40 images built, then died bringing containers up
                   on `Bind for 0.0.0.0:3181 failed: port is already
                   allocated`; another run held the healthcheck ports
  run 314  branch  `frontend grpc server closed unexpectedly`, with four
                   runs in flight at once

CLAUDE.md already records the same shape for `no space left on device`
and `Internal: unknown blob sha256:...`. None of it says anything about
the change under test, and each one costs a full re-run -- which is to
say most of the time spent waiting for this pipeline was spent waiting
for it twice.

So: one lane for the workflow. Runs queue instead of overlapping.

cancel-in-progress stays false. Cancelling a main run mid-publish leaves
some targets pushed and some not, which is how the registry ended up
holding an incomplete set of build-stamped tags (265 and 270, both killed
7s into `pushing layers`). Queued runs still do not pile up: while one
holds the lane only the newest queued run survives. The trade is that
pushing again no longer kills a run that has already started.

Note the healthcheck host ports are still fixed and still global to the
daemon; serialising is what stops them colliding. Anything else sharing
that daemon can still collide with us.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Grey.ooo/docker!45
No description provided.