Skip to content

fix(ci): overlay images from the PR base branch, and publish per-line artifacts - #3471

Merged
myasnikovdaniil merged 3 commits into
mainfrom
ci/overlay-main-images-base-guard
Aug 3, 2026
Merged

fix(ci): overlay images from the PR base branch, and publish per-line artifacts#3471
myasnikovdaniil merged 3 commits into
mainfrom
ci/overlay-main-images-base-guard

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What this PR does

The PR finalize job overlays image refs for every package a PR did not rebuild, so e2e and the installer do not test last-release images for everything outside the PR's build matrix. It could only ever read cozystack-packages:main, which meant a release-line PR was handed main's binaries to run against its own line's charts.

#3437 is the demonstration: a one-line change on release-1.6 deactivating an app failed install deterministically, twice, with

helmrelease/backupstrategy-controller: Helm install failed …
error validating data: SchemaError(github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.Option.spec):
unknown model in reference: "github.com~1cozystack~1cozystack~1pkg~1apis~1core~1v1alpha1.OptionSpec"

main's cozystack-controller served an aggregated OpenAPI that release-1.6's charts could not validate against. Nothing in that PR was broken; the lane was. Both branches carry option_types.go and key OptionSpec identically in the committed generated OpenAPI, so this is a generation mismatch at runtime, not a codegen drift.

This PR fixes it by giving every base branch its own artifact to overlay from, rather than by turning the overlay off.

The change

  1. pull-requests.yaml reads cozystack-packages:${BASE_REF} (github.base_ref) instead of a hardcoded :main.
  2. build-release.yaml (new) publishes that artifact for maintained release-<major>.<minor> branches exactly as build-main.yaml does for main: every image tagged with the branch, and the whole packages tree pushed with each reference digest-pinned to what the run just built.

An earlier revision of this branch simply skipped the overlay for non-main bases. That fixed the wrong-images problem but left release-line PRs testing their line's last release: for any package the PR did not rebuild, the committed ref is the released digest, so a component changed by an earlier backport was still exercised as its pre-backport binary until the next rc. Per-line artifacts remove that gap too, which is why the guard was replaced rather than kept.

Three deliberate choices

The trigger matches line branches only (release-[0-9]+.[0-9]+). The per-release and rc staging branches promote-rc.yaml and tags.yaml create — release-1.6.1, release-1.6.0-rc.4 — must not trigger a full rebuild; their images come from the tag build and rebuilding them is waste.

WRITE_CACHE stays 0. CACHE_REGISTRY/<img>:buildcache is a single ref per image and build-main.yaml is deliberately its only, serialized writer so concurrent builds cannot race on the cache manifest — the 409 class #2711 fixed for image tags. A line build can overlap a main build, so writing here would reintroduce that race. Line builds read the cache.

A missing artifact still degrades to committed refs, but says so. On a release line it emits a ::warning:: naming the branch. Silent degradation is indistinguishable from a working overlay, which is how a mis-specified branch filter could hide for a whole release cycle.

Cost

One make build per push to a maintained line — in practice per merged backport. That is the price of release-line PRs testing their line's tip instead of its last release.

Verification

hack/overlay-main-images_test.bats pins the artifact tag to the base branch, rejects a hardcoded :main in either overlay step, and pins build-release.yaml's branch filter, image tag and WRITE_CACHE: '0'. Mutation-checked: restoring :main, setting WRITE_CACHE: '1', and broadening the filter to release-* each fail a test. 13/13 green; actionlint and zizmor clean.

Worth an explicit ack in review: the branch-filter pattern is the one thing no local test can prove, since only GitHub evaluates it. If it does not match, build-release never runs and the new ::warning:: is what surfaces it on the next release-line PR.

Backport

release-1.6 needs this too — for pull_request events GitHub builds the workflow from the merge ref, so a release-line PR only stops receiving main's images once the change is on its base branch. #3472 carried the interim guard and is closed in favour of backporting this instead.

Release note

NONE

Summary by CodeRabbit

  • New Features

    • Added a “Build release line” workflow that builds and publishes images and the packages artifact for maintained release-<major>.<minor> branches.
  • Bug Fixes

    • Updated PR workflow finalization to use base-branch–specific package overlays, avoiding incorrect main-branch image references when targeting release branches.
    • Improved fallback behavior when the base-branch packages artifact is unavailable.
  • Tests

    • Added workflow wiring tests to verify base-branch artifact usage and that each maintained release line publishes its own packages artifact.

The PR finalize job pulls cozystack-packages:main and repoints every package
the PR did not rebuild at current-main images. On a main-based PR that is the
whole point: it stops e2e from testing last-release images for everything
outside the build matrix. On a release-line PR it is wrong — the committed refs
there are not stale, they are that line`s released digests, and the charts are
written against exactly those.

So a release-line PR was installing main`s binaries onto its own charts, and the
mismatch grows with every commit main gains. #3437 is the demonstration: a
one-line change on release-1.6 that deactivates an app failed install
deterministically, twice, with

  SchemaError(...core/v1alpha1.Option.spec): unknown model in reference:
  "...core~1v1alpha1.OptionSpec"

from main`s cozystack-controller serving an aggregated OpenAPI that branch`s
charts cannot validate against. Nothing in the PR was broken; the lane was.
Left alone this makes every 1.6 backport look red, which is when release-branch
PRs are busiest.

Both overlay steps are now gated on `github.base_ref == main`, so a release-line
PR keeps its committed digests — the behaviour that predates the overlay.
Retargeting the overlay at a per-line artifact would be better but is not
possible today: build-main.yaml publishes only cozystack-packages:main, and the
registry carries no release-* equivalent (verified against the packages repo`s
tag list: `main` plus per-PR tags, nothing else).

hack/overlay-main-images_test.bats pins the wiring per step, so adding a third
overlay step without the guard fails the suite. Mutation-checked by removing one
guard. 12/12 green; actionlint clean.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions github-actions Bot added size/M This PR changes 30-99 lines, ignoring generated files area/ci Issues or PRs related to CI workflows, GitHub Actions, automation kind/bug Categorizes issue or PR as related to a bug labels Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Release-line builds now publish branch-tagged package artifacts with cache writes disabled. Pull-request finalization retrieves the artifact for the pull request’s base branch, falls back to committed refs on failure, and overlays base-branch refs for unbuilt packages. Bats tests validate the workflow wiring.

Changes

Release-aware package artifacts

Layer / File(s) Summary
Publish release-line package artifacts
.github/workflows/build-release.yaml, hack/overlay-main-images_test.bats
A new workflow builds maintained release-<major>.<minor> branches, publishes images and packages under github.ref_name, and sets WRITE_CACHE to 0.
Retrieve and overlay base-branch packages
.github/workflows/pull-requests.yaml
The finalize job pulls cozystack-packages:${BASE_REF} using github.base_ref, handles pull failures with branch-specific messages, and overlays the downloaded refs for unbuilt packages.
Validate workflow wiring
hack/overlay-main-images_test.bats
Bats coverage checks base-branch artifact selection, rejects a pinned cozystack-packages:main reference, and validates release workflow triggers and settings.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: androndo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main CI changes: base-branch image overlays and per-line artifact publishing.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/overlay-main-images-base-guard

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@hack/overlay-main-images_test.bats`:
- Around line 157-177: Harden the wiring test around the overlay step loop so it
validates a standalone YAML if: key rather than any matching text in the step
block. Add a mutation check for each step that removes its base-branch guard and
asserts the test fails, ensuring the guard is structurally required and cannot
be satisfied by run content or comments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 246369e4-4f9a-44d1-9b4a-d734a1e33c85

📥 Commits

Reviewing files that changed from the base of the PR and between 2c45377 and fea40d6.

📒 Files selected for processing (2)
  • .github/workflows/pull-requests.yaml
  • hack/overlay-main-images_test.bats

Comment thread hack/overlay-main-images_test.bats Outdated
IvanHunters
IvanHunters previously approved these changes Jul 28, 2026

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The if: github.base_ref == 'main' guard targets the root cause, not a symptom: the current-main overlay's premise (committed refs are stale, repoint them at main digests) only holds for main-based PRs. On release-line PRs it was deploying main binaries onto release charts — the failure mode #3437 hit.

Verified: pull_request trigger always populates base_ref; release branches use the release-* naming so == 'main' is an exact discriminator; _out/mainpkgs is consumed only by these two steps, so release-line PRs skip both cleanly with no dangling refs. bats 12/12 green incl. the new guard test (mutation-checked). No regressions — main-based behaviour is byte-identical.

… artifacts

Supersedes the interim guard in this branch`s first commit, which skipped the
overlay entirely for release-line PRs. Skipping fixed the wrong images but left
those PRs testing their line`s last release: for any package the PR did not
rebuild, the committed ref is the released digest, so a component changed by an
earlier backport was exercised as its pre-backport binary until the next rc.

The overlay now reads `cozystack-packages:<base branch>` instead of always
`:main`, and build-release.yaml publishes that artifact for every maintained
`release-<major>.<minor>` branch the way build-main.yaml does for main: images
tagged with the branch, and the whole packages tree pushed with each reference
digest-pinned to what the run just built. Each base branch therefore has its own
generation to overlay from, which is what the original bug was really about —
#3437 failed install because main`s cozystack-controller served an aggregated
OpenAPI release-1.6`s charts could not validate against.

Three deliberate choices:

  * The trigger matches line branches only (`release-[0-9]+.[0-9]+`). The
    per-release and rc staging branches promote-rc.yaml and tags.yaml create
    (release-1.6.1, release-1.6.0-rc.4) must not trigger a full rebuild — their
    images come from the tag build, and rebuilding them would be waste.
  * WRITE_CACHE stays 0. CACHE_REGISTRY/<img>:buildcache is a single ref per
    image and build-main.yaml is deliberately its only, serialized writer so
    concurrent builds cannot race on the cache manifest (the 409 class #2711
    fixed for image tags). A line build can overlap a main build, so writing here
    would reintroduce that race. Line builds read the cache.
  * A missing artifact still degrades to committed refs, but on a release line it
    now emits a ::warning:: naming the branch. Silent degradation is
    indistinguishable from a working overlay, which is how a mis-specified branch
    filter would hide for a whole release cycle.

Cost: one `make build` per push to a maintained line, i.e. per merged backport.

hack/overlay-main-images_test.bats pins the artifact tag to the base branch,
rejects a hardcoded :main in either overlay step, and pins build-release.yaml`s
branch filter, image tag and WRITE_CACHE=0. Mutation-checked: restoring :main,
setting WRITE_CACHE=1, and broadening the filter to release-* each fail a test.
13/13 green; actionlint and zizmor clean.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
@github-actions github-actions Bot added size/L This PR changes 100-499 lines, ignoring generated files and removed size/M This PR changes 30-99 lines, ignoring generated files labels Jul 29, 2026
@myasnikovdaniil myasnikovdaniil changed the title fix(ci): only overlay current-main images on main-based PRs fix(ci): overlay images from the PR base branch, and publish per-line artifacts Jul 29, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build-release.yaml:
- Around line 69-72: Update the “Set up build toolchain” step to stop piping the
mutable fluxcd.io installer directly into sudo bash; download a version- or
commit-pinned Flux installation asset, verify its SHA-256 checksum against a
pinned expected value, and only then execute it, preserving the existing
command-availability check.

In @.github/workflows/pull-requests.yaml:
- Around line 405-409: Update the BASE_REF == "main" branch in the
missing-artifact handling to emit the same GitHub Actions ::warning annotation
as the release-line branch, while retaining the existing context that committed
refs are being kept. Ensure missing artifacts on main trigger a workflow warning
rather than stdout-only logging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eff05ab2-66be-4c0c-ae84-47400ae21b9a

📥 Commits

Reviewing files that changed from the base of the PR and between fea40d6 and 2330d6f.

📒 Files selected for processing (3)
  • .github/workflows/build-release.yaml
  • .github/workflows/pull-requests.yaml
  • hack/overlay-main-images_test.bats

Comment on lines +69 to +72
- name: Set up build toolchain
run: |
command -v flux >/dev/null \
|| curl -fsSL https://fluxcd.io/install.sh | sudo bash

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- workflow context ---\n'
sed -n '1,140p' .github/workflows/build-release.yaml 2>/dev/null || true

printf '\n--- matching workflow references ---\n'
rg -n "curl .*install\.sh|fluxcd|OCIR|registry|docker|podman|cosign|cosign|ghcr|docker login|registry-oci|ocir|token|password|secrets" .github/workflows/build-release.yaml .github/workflows 2>/dev/null || true

Repository: cozystack/cozystack

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- workflow context ---'
sed -n '1,140p' .github/workflows/build-release.yaml 2>/dev/null || true

echo
echo '--- matching workflow references ---'
rg -n "url .*install\.sh|fluxcd|OCIR|registry|docker|podman|cosign|ghcr|docker login|registry-oci|ocir|token|password|secrets" .github/workflows/build-release.yaml .github/workflows 2>/dev/null || true

Repository: cozystack/cozystack

Length of output: 38144


Pin and verify the Flux installer.

.github/workflows/build-release.yaml:72 pipes a mutable https://fluxcd.io/install.sh script directly into sudo bash before logging into OCIR. Use a version/pinned-build asset plus SHA-256 verification, or a commit-pinned trusted action, so a compromised installer cannot persist on the runner and compromise later registry-authenticated build/push steps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-release.yaml around lines 69 - 72, Update the “Set
up build toolchain” step to stop piping the mutable fluxcd.io installer directly
into sudo bash; download a version- or commit-pinned Flux installation asset,
verify its SHA-256 checksum against a pinned expected value, and only then
execute it, preserving the existing command-availability check.

Comment on lines +405 to +409
if [ "${BASE_REF}" = "main" ]; then
echo "cozystack-packages:${BASE_REF} pull failed — keeping committed refs"
else
echo "::warning title=No packages artifact for ${BASE_REF}::Keeping committed refs, so unbuilt packages use this line's last release rather than its tip. Check that build-release.yaml has run for ${BASE_REF}."
fi

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Emit a workflow warning for a missing main artifact too.

The main fallback only logs stdout on Line 406, unlike the release-line path’s ::warning annotation. This misses the stated missing-artifact warning contract and makes a degraded main PR easier to overlook.

Proposed fix
           if [ "${BASE_REF}" = "main" ]; then
-            echo "cozystack-packages:${BASE_REF} pull failed — keeping committed refs"
+            echo "::warning title=No packages artifact for ${BASE_REF}::Keeping committed refs; unbuilt packages may use the last released images."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ "${BASE_REF}" = "main" ]; then
echo "cozystack-packages:${BASE_REF} pull failed — keeping committed refs"
else
echo "::warning title=No packages artifact for ${BASE_REF}::Keeping committed refs, so unbuilt packages use this line's last release rather than its tip. Check that build-release.yaml has run for ${BASE_REF}."
fi
if [ "${BASE_REF}" = "main" ]; then
echo "::warning title=No packages artifact for ${BASE_REF}::Keeping committed refs; unbuilt packages may use the last released images."
else
echo "::warning title=No packages artifact for ${BASE_REF}::Keeping committed refs, so unbuilt packages use this line's last release rather than its tip. Check that build-release.yaml has run for ${BASE_REF}."
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pull-requests.yaml around lines 405 - 409, Update the
BASE_REF == "main" branch in the missing-artifact handling to emit the same
GitHub Actions ::warning annotation as the release-line branch, while retaining
the existing context that committed refs are being kept. Ensure missing
artifacts on main trigger a workflow warning rather than stdout-only logging.

@myasnikovdaniil myasnikovdaniil added the backport Should change be backported on previous release label Jul 29, 2026
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Pushed a second commit that changes the approach — worth re-reading rather than diffing, since the mechanism is now different. Title and description updated to match; backport label added so this reaches release-1.6 on merge.

Before (fea40d631): skip the current-main overlay when github.base_ref != 'main'. That stopped main's binaries being installed against a release line's charts, but it left those PRs testing the line's last release — for any package a PR does not rebuild, the committed ref is the released digest, so a component changed by an earlier backport is exercised as its pre-backport binary until the next rc. The overlay exists precisely to avoid that on main, and the guard reintroduced it on release lines.

Now (2330d6f3c): the overlay reads cozystack-packages:${{ github.base_ref }} instead of a hardcoded :main, and a new build-release.yaml publishes that artifact for every maintained release-<major>.<minor> branch the way build-main.yaml does for main — images tagged with the branch, whole packages tree pushed with each reference digest-pinned to what the run just built. Each base branch overlays its own generation, so both the cross-generation mismatch (#3437) and the staleness are gone. The if: guard became a parameterisation.

Three decisions to check, all load-bearing:

  • WRITE_CACHE: '0' in the new workflow. CACHE_REGISTRY/<img>:buildcache is one ref per image and build-main.yaml is deliberately its only, serialized writer so concurrent builds cannot race on the cache manifest (the 409 class refactor(build): standardize image tagging to fix concurrent PR push conflicts #2711 fixed for image tags). A line build can overlap a main build, so writing there would reintroduce that race. Line builds read the cache.
  • Trigger restricted to release-[0-9]+.[0-9]+. The per-release and rc staging branches promote-rc.yaml and tags.yaml create (release-1.6.1, release-1.6.0-rc.4) must not trigger a full rebuild; their images come from the tag build.
  • A missing artifact warns. It still degrades to committed refs, but on a release line it emits a ::warning:: naming the branch, because a silent no-op is indistinguishable from a working overlay — which is how a mis-specified branch filter would hide for an entire release cycle.

The branch-filter pattern is the one claim no local test can settle, since only GitHub evaluates it. If it does not match, build-release never runs and that warning is what surfaces it on the next release-line PR.

Verification: hack/overlay-main-images_test.bats pins the artifact tag to the base branch, rejects a hardcoded :main in either overlay step, and pins the new workflow's filter, image tag and WRITE_CACHE. Mutation-checked — restoring :main, setting WRITE_CACHE: '1', and broadening the filter to release-* each fail a test. 13/13 green, actionlint and zizmor clean.

Cost, stated plainly: one make build per push to a maintained line, i.e. per merged backport.

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.

LGTM — the base-branch resolution is real code and not a behaviour pinned only by tests, the two new wiring tests actually go red when the thing they protect is removed, and nothing here changes what gets published to the release registry.

Business context: the PR-finalize overlay could only ever read the main packages artifact, so a PR based on a release line was handed main's binaries to run against that line's charts; this makes the overlay read the PR's own base branch and adds the workflow that publishes the per-line artifact it needs.

What I checked

The problem reproduces from source on main, not just from the description. The pull_request trigger carries no branch filter, and the finalize step pulls cozystack-packages:main unconditionally before overlaying it onto every package the PR did not rebuild. So a release-line PR gets the cross-generation mix — and the quieter half of that is that a green e2e on such a PR proves nothing about the line it targets.

The base-branch resolution is implemented, not merely asserted. pull-requests.yaml pulls cozystack-packages:${BASE_REF} with BASE_REF: ${{ github.base_ref }} wired into that step's env. hack/overlay-main-images.sh is untouched and does not need to change: it consumes an extracted directory and never resolves a branch itself. The producer side closes the loop — build-release.yaml sets IMAGE_TAG: ${{ github.ref_name }}, the root build target ends in make -C packages/core/installer image, and that Makefile's image-packages pushes oci://$(REGISTRY)/cozystack-packages:$(IMAGE_TAG).

The changed step did execute on this head: the finalize job logged pulling artifact from …/cozystack-packages:main followed by overlaid=4 same=127 skipped=40 drift=0 failed=0, so the main path is proven by a real run and not only by a YAML assertion. Neither the release-line path nor build-release.yaml is exercised by any PR run — the latter is push-triggered on a line branch, so it cannot run until it is on one. That is worth stating plainly since the description already acknowledges the branch filter as locally unprovable.

I mutation-checked the two new tests rather than trusting the green. Reverting the tag to :main, dropping BASE_REF from that step's env, renaming the step, broadening the trigger to release-*, flipping WRITE_CACHE to '1', pointing IMAGE_TAG away from github.ref_name, and deleting build-release.yaml each turn the file red with a TAP not ok and 12 of the declared 13 ok lines — under plain bats and under hack/cozytest.sh, which is what make unit-tests runs. These are not assert-the-guard-exists tests.

The branch filter is the same glob shape GitHub documents for a version pattern: the character class matches one digit, + repeats it, . is literal, and the match is anchored to the whole ref. It selects release-1.6 and rejects release-1.6.1 and release-1.6.0-rc.4, all three of which exist on the remote right now.

The backport label is applicable as written: the squashed diff applies cleanly against the current release-1.6 tree, and that branch's build-main.yaml, hack/common-envs.mk and installer Makefile are identical in every part this depends on, including the runner label and the artifact push. The first merge into the line bootstraps the line's own artifact, so there is no chicken-and-egg step to perform by hand.

Merging this to main alone fixes nothing — a push workflow runs the copy present on the pushed branch, and a pull_request run uses the base branch's copy — so the backport is the delivery, not a follow-up. I do not treat that as a blocker: it is stated in the description, the label that automates it is already on the PR, and the new annotation is what surfaces the gap if the rollout stalls.

Blast radius on release engineering is nil. Everything here pushes to the CI registry; stable publishing runs against the public registry from the release finalize path, which this does not touch.

Non-blocking follow-ups

  1. The naming this change invalidates deserves a sweep. hack/overlay-main-images.sh still names the main artifact as its source of truth in both the header and the usage block, still prints Overlay current-main images: and -> current-main per file, and docs/agents/image-refs.md still describes the tool as overlaying "current-main references". This PR's own finalize log shows overlay: packages/system/seaweedfs/values.yaml -> current-main; on a release-line PR that same line will say "current-main" while overlaying that line's refs. I am not blocking on descriptive text and the corrective paragraph is right there in the workflow — but the incident being fixed here was itself a misdiagnosis, so a log line that lies about which generation was applied is the wrong thing to leave behind.

  2. The comment this diff added on the overlay step says the script handles "the empty directory the step above leaves when a line has no artifact yet". The step does rm -rf _out/mainpkgs on failure, so it leaves no directory at all, and the script's guard is the missing-directory branch. Behaviour is right, the comment is not, and it is a comment this diff wrote. Related: the paragraph directly above the changed step still asserts :main as the source and is contradicted by the new paragraph under it.

  3. Trigger breadth against cost. paths-ignore: ['docs/**'] means any non-docs push to a maintained line fires the full build — a workflow edit, a test-only change, a merged promote PR. On main that breadth pays for itself because the run warms the shared cache; here WRITE_CACHE is '0', so a run that produces no new image is pure cost on a large runner. Narrowing to the paths that can actually change an image would remove most of it without weakening the guarantee.

  4. On a fork PR the registry login step is skipped, so flux pull fails for want of credentials. Against main that is the quiet echo, but against a release line it now emits "Check that build-release.yaml has run for …", which points at the wrong cause. Distinguishing "no credentials" from "no artifact" costs one condition.

  5. build-release.yaml is close to a copy of build-main.yaml and nothing pins the two in sync, so a future fix to the toolchain or buildx setup in one silently misses the other. Same for the missing repository guard: a fork that syncs a release line triggers a large, long job that dies at login with empty secrets. Both files share that gap, so this mirrors the house pattern rather than introducing one.

  6. The ::warning on a missing artifact is presented as a deliberate design choice, but nothing pins it — a later edit can drop it silently, which is the same class of gap the rest of this change closes.

  7. The two open review-bot comments still stand. On pinning the flux installer in particular: this job holds registry push credentials and the artifact it produces becomes the image source for every PR on that line, so an unpinned installer piped into sudo bash matters more here than its age in build-main.yaml suggests. If it gets fixed, fixing both files in one pass is the cheaper move.

The latest run on this head came from a `labeled` event carrying a label other
than full-e2e. pull-requests.yaml discards those by design — `plan` skips, and
checks, build, build-talos, finalize and e2e all skip behind it — so the run
reported nothing. Two side effects made that worse than a wasted run: the
required "E2E Tests" context was left SKIPPED, which branch protection counts
as satisfied, and cancel-in-progress killed the in-flight api-review-gate run
that was carrying the real signal.

Empty commit to get a genuine run against this head. No content change.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
@myasnikovdaniil
myasnikovdaniil deleted the ci/overlay-main-images-base-guard branch August 3, 2026 13:22
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Backport failed for release-1.6, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release-1.6
git worktree add -d .worktree/backport-3471-to-release-1.6 origin/release-1.6
cd .worktree/backport-3471-to-release-1.6
git switch --create backport-3471-to-release-1.6
git cherry-pick -x fea40d6314424a4105d31c7ee77f818062f5bc1a 2330d6f3ca42306da92190b5e9dddf0bc1054fcd 007d0b1a22ec1aadca95960645d8232a5b202fcb

myasnikovdaniil added a commit that referenced this pull request Aug 3, 2026
…h, and publish per-line artifacts (#3514)

## What this PR does

Hand backport of #3471 to `release-1.6`. Two clean cherry-picks, each
carrying its `-x` reference; the resulting tree is byte-identical to
#3471's merged state for all three files.

The overlay must be fixed **on this branch** to have any effect here.
For `pull_request` events GitHub builds the workflow from the merge ref,
and for `push` events it reads the workflow from the pushed ref — so
neither half of #3471 reaches this line while it lives only on `main`.
Concretely, `build-release.yaml` on `main` never fires for a push to
`release-1.6`, and a `release-1.6` PR keeps using this branch's copy of
`pull-requests.yaml`.

## Why the bot could not do this

The automatic backport reported success on every job and opened nothing
(run 30817682877). It is not a conflict — #3471 carries an empty commit,
`007d0b1a` (`chore(ci): re-trigger CI after a label event produced a
no-op run`, zero files). `korthout/backport-action` cherry-picks
commit-by-commit without `--allow-empty`, so that commit fails with `The
previous cherry-pick is now empty`; the `draft_commit_conflicts`
fallback then runs `git commit --all -m BACKPORT-CONFLICT`, which also
has nothing to commit, and the action aborts:

```
git cherry-pick -x 007d0b1
The previous cherry-pick is now empty, possibly due to conflict resolution.
git commit --all -m BACKPORT-CONFLICT
The previous cherry-pick is now empty, possibly due to conflict resolution.
git cherry-pick --abort
Backport failed for `release-1.6`, because it was unable to cherry-pick the commit(s).
```

This is a general gap, not specific to this PR: any labelled PR
containing an empty commit backports to nothing, and the failure
surfaces as three green jobs. Worth a follow-up on the workflow —
skipping empty commits, or having the action's failure fail the job —
but that is out of scope here.

## What this unblocks

#3437 fails install deterministically on this line today, because the
unguarded overlay hands it main's `cozystack-controller` to run against
`release-1.6`'s charts. The binary indexes
`internal.cozystack.io/v1alpha1 TenantProjection` at startup, this
branch ships no such CRD, so it fatals and every release that depends on
it stalls. With this merged, the overlay reads
`cozystack-packages:release-1.6` instead.

That works even before the first `build-release.yaml` run publishes the
artifact: a missing artifact degrades to the committed refs, which on
this branch are the v1.6.0 digests its charts are written against. The
degraded path is also no longer silent — it emits a warning naming the
branch.

#3437 will need a new head commit rather than a re-run, since the
finalize job is reused on re-run and its existing `pr.patch` already has
main's refs baked in.

## Note on cost

Merging this is itself a push to `release-1.6`, so `build-release.yaml`
fires on it and runs a full `make build` (up to 2h on a 24-cpu runner)
to publish the line's images and `cozystack-packages:release-1.6`. That
recurs per push to this line; `concurrency` with `cancel-in-progress`
collapses bursts, and `paths-ignore` only exempts `docs/**`.

## Verification

`hack/overlay-main-images_test.bats` 13/13 green on this branch,
including the two wiring tests the backport brings (`the overlay reads
the artifact for the PR's own base branch`, `every maintained release
line publishes its own packages artifact`). `actionlint` clean on both
workflows, and both parse as YAML. `hack/common-envs.mk` is
byte-identical between `main` and `release-1.6`, so `IMAGE_TAG`,
`WRITE_CACHE`, `PUBLISH_VERSIONED` and `PUBLISH_FLOATING` behave here
exactly as they do on main.

### Release note

```release-note
NONE
```
myasnikovdaniil added a commit that referenced this pull request Aug 4, 2026
build-release.yaml publishes `cozystack-packages:<line>` on push to a
release line, and pull-requests.yaml's overlay reads it so a release-line
PR tests its own line's binaries instead of main's (#3471, #3437).

It cannot fire for the push that creates the line. The freeze points
release-X.Y at a commit that is already on main, so the push carries no
new commits, and GitHub does not run a workflow whose paths/paths-ignore
filter finds no changed files ("If there are no files changed, the
workflow will not run"). The line would therefore have no artifact until
its first cherry-pick merged, and in that window the overlay finds
nothing to pull and leaves every package on its committed ref — at
freeze time the previous release's, which is exactly the
cross-generation mix #3437 fixed. The old flow had no such window: it
branched at the promote merge commit, which carried real commits and its
own release's refs.

Add workflow_dispatch to build-release.yaml and have the freeze step
dispatch it for the branch it just created, so the artifact exists from
the moment the line does. The dispatch is non-fatal: without it the
overlay no-ops and early cherry-pick PRs test their committed refs,
which is where they were before #3471. The tag is pushed and the line is
frozen by that point, so failing there would misreport both.

The trigger also gives a line build a re-run button, which previously
needed an empty commit pushed to the line.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci Issues or PRs related to CI workflows, GitHub Actions, automation backport Should change be backported on previous release kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants