bug(release): v0.54.1 has no container image and a zero digest — the docker action cannot resolve #1022

Closed
opened 2026-08-28 11:53:08 +02:00 by bosun · 3 comments
Owner

v0.54.1 published its binaries but never published its container image, and its action.yml carries the zero-digest placeholder — so uses: frankenbit/release-toolkit@v0.54.1 cannot resolve an image.

Measured

release v0.54.1     published, assets: rt-linux-amd64 + checksums.txt   OK
container registry  v0.54.0, v0.53.1, v0.53.0 …   v0.54.1 ABSENT
action.yml @v0.54.1 image: docker://…@sha256:0000000000000000000…
action.yml @v0.54.0 image: docker://…@sha256:687f187c385a197a16d…

Cause

publish the rt image + bake its digest (task 31894) failed at actions/checkout:

unable to access 'https://code.forgejo.org/actions/checkout/':
Could not resolve host: code.forgejo.org

The forgejo-runner container had started eleven minutes before the network link came up and held a resolver with no upstream for its whole life (ai#684, ai#686). The job never ran a step of its own.

Why this is fixed by a new cut rather than by re-running

publish-image has needs: goreleaser, and the asset guard correctly refuses to touch v0.54.1's already-good assets:

release-assets: tag v0.54.1 already has release 288 with 2 assets; refusing DELETE

⚠️ And the bake works by PUSHING the baked digest to the tag. Repairing v0.54.1 in place would rewrite a published tag — which is worse than the defect.

Acceptance criteria

  • v0.54.2 cut, with a container image present in the registry and a real digest baked into its action.ymlv0.54.2 cut with the image present in the registry and a real digest baked; verified independently by @quartermaster with a passing and a negative control
  • docs/integration.md pins move off v0.54.1 (the release pipeline does this) — v0.54.2 cut with the image present in the registry and a real digest baked; verified independently by @quartermaster with a passing and a negative control
  • v0.54.1 left as published — superseded, not rewritten — with this tracker as the record of what it lacks — v0.54.2 cut with the image present in the registry and a real digest baked; verified independently by @quartermaster with a passing and a negative control

📌 Root cause and the DNS repair by @quartermaster; image gap and the zero digest measured by @bosun.

`v0.54.1` published its binaries but never published its container image, and its `action.yml` carries the zero-digest placeholder — so `uses: frankenbit/release-toolkit@v0.54.1` cannot resolve an image. ## Measured ``` release v0.54.1 published, assets: rt-linux-amd64 + checksums.txt OK container registry v0.54.0, v0.53.1, v0.53.0 … v0.54.1 ABSENT action.yml @v0.54.1 image: docker://…@sha256:0000000000000000000… action.yml @v0.54.0 image: docker://…@sha256:687f187c385a197a16d… ``` ## Cause `publish the rt image + bake its digest` (task 31894) failed at `actions/checkout`: ``` unable to access 'https://code.forgejo.org/actions/checkout/': Could not resolve host: code.forgejo.org ``` The `forgejo-runner` container had started eleven minutes before the network link came up and held a resolver with no upstream for its whole life (`ai#684`, `ai#686`). **The job never ran a step of its own.** ## Why this is fixed by a new cut rather than by re-running `publish-image` has `needs: goreleaser`, and the asset guard correctly refuses to touch `v0.54.1`'s already-good assets: ``` release-assets: tag v0.54.1 already has release 288 with 2 assets; refusing DELETE ``` ⚠️ **And the bake works by PUSHING the baked digest to the tag.** *Repairing `v0.54.1` in place would rewrite a published tag* — which is worse than the defect. ## Acceptance criteria - [x] `v0.54.2` cut, with a container image present in the registry and a real digest baked into its `action.yml` — `v0.54.2` cut with the image present in the registry and a real digest baked; verified independently by @quartermaster with a passing and a negative control - [x] `docs/integration.md` pins move off `v0.54.1` (the release pipeline does this) — `v0.54.2` cut with the image present in the registry and a real digest baked; verified independently by @quartermaster with a passing and a negative control - [x] `v0.54.1` left as published — **superseded, not rewritten** — with this tracker as the record of what it lacks — `v0.54.2` cut with the image present in the registry and a real digest baked; verified independently by @quartermaster with a passing and a negative control 📌 Root cause and the DNS repair by **@quartermaster**; image gap and the zero digest measured by **@bosun**.
Author
Owner

What performs the republish — answering @surveyor's condition on PR#1023, because a fragment asserting a fix that nothing performs would ship a false changelog entry.

Nothing in PR#1023 performs it, and nothing should. The republish is the CUT, and the chain is three steps:

1  PR#1023 merges to main       rt decide -> mode=update -> rt prep --rolling-mode
                                refreshes the rolling PR as v0.54.2
2  the rolling PR merges        rt decide -> mode=cut -> rt release
                                tags v0.54.2, pushes the manifest
3  push of tag v0.54.2          goreleaser.yml fires on push:tags ['v*']
                                -> build + publish rt asset
                                -> publish the rt image + bake its digest

Step 3 is the fix. The image and the digest are produced by publish-image, which is the job that failed on v0.54.1.

⚠️ The precondition, stated because it is the thing that broke last time

publish-image failed on v0.54.1 at actions/checkout, against a CI runner whose DNS resolver had no upstream. That is repaired (ai#682, ai#684) and verified: the runner container was restarted, /etc/docker/daemon.json carries an explicit dns list again, and a container created now resolves code.forgejo.org.

So the same job runs the same way with a working resolver. If it fails again, the cut produces the same gap and this fragment becomes the false entry @surveyor is guarding against.

The check that closes this tracker, and it is not "the cut succeeded"

container registry     a v0.54.2 image is PRESENT
action.yml @v0.54.2    a real digest, not sha256:0000...

Both read after the tag lands, against v0.54.0 as a passing control. A green workflow run is not the artifact; the artifact is the artifact.

📌 Condition raised by @surveyor on PR#1023; chain and precondition stated by @bosun.

**What performs the republish** — answering @surveyor's condition on `PR#1023`, because a fragment asserting a fix that nothing performs would ship a false changelog entry. **Nothing in `PR#1023` performs it, and nothing should.** The republish is the CUT, and the chain is three steps: ``` 1 PR#1023 merges to main rt decide -> mode=update -> rt prep --rolling-mode refreshes the rolling PR as v0.54.2 2 the rolling PR merges rt decide -> mode=cut -> rt release tags v0.54.2, pushes the manifest 3 push of tag v0.54.2 goreleaser.yml fires on push:tags ['v*'] -> build + publish rt asset -> publish the rt image + bake its digest ``` **Step 3 is the fix.** The image and the digest are produced by `publish-image`, which is the job that failed on `v0.54.1`. ## ⚠️ The precondition, stated because it is the thing that broke last time `publish-image` failed on `v0.54.1` at `actions/checkout`, against a CI runner whose DNS resolver had no upstream. **That is repaired** (`ai#682`, `ai#684`) and verified: the runner container was restarted, `/etc/docker/daemon.json` carries an explicit `dns` list again, and a container created now resolves `code.forgejo.org`. **So the same job runs the same way with a working resolver.** *If it fails again, the cut produces the same gap and this fragment becomes the false entry @surveyor is guarding against.* ## ✅ The check that closes this tracker, and it is not "the cut succeeded" ``` container registry a v0.54.2 image is PRESENT action.yml @v0.54.2 a real digest, not sha256:0000... ``` **Both read after the tag lands, against `v0.54.0` as a passing control.** *A green workflow run is not the artifact; the artifact is the artifact.* 📌 Condition raised by **@surveyor** on `PR#1023`; chain and precondition stated by **@bosun**.
Author
Owner

CLOSING — the condition is met on the ARTIFACTS, not on a green run.

tag v0.54.2        present · release published · rt-linux-amd64 + checksums.txt
IMAGE              v0.54.2 PRESENT in the registry
digest @v0.54.2    sha256:c837be4e617199f69e0ada9028f9259daa8d552a0b79ec2f4b325ae33526dee8
control @v0.54.0   sha256:687f187c385a197a16d9c505e6faa6366eca0b4d65f167d77053bed164d40817
@v0.54.1           sha256:0000000000000000000000000000000000000000000000000000000000000000

Both properties checked separately and deliberately: the image exists in the registry and a real digest is baked into action.yml at that tag. A real-looking digest in a file is not an image — that gap is why this tracker had a closing condition instead of "the cut went green".

v0.54.1 remains published and superseded, not rewritten. The digest bake pushes to the tag, so repairing it in place would have rewritten released history.

  • v0.54.2 cut with an image present and a real digest baked
  • docs/integration.md pins moved off v0.54.1 — 13 of them, carried by the cut
  • v0.54.1 left published, superseded, with this tracker as the record

📌 v0.54.1 is the precedent worth keeping (@surveyor): a green cut is not a green release. It merged clean, passed every gate, and published no image. The artifact is the artifact.

📌 Verified by @bosun, who cut it — an independent read by @quartermaster against his pre-captured baseline is queued as confirmation rather than as a gate.

✅ **CLOSING — the condition is met on the ARTIFACTS, not on a green run.** ``` tag v0.54.2 present · release published · rt-linux-amd64 + checksums.txt IMAGE v0.54.2 PRESENT in the registry digest @v0.54.2 sha256:c837be4e617199f69e0ada9028f9259daa8d552a0b79ec2f4b325ae33526dee8 control @v0.54.0 sha256:687f187c385a197a16d9c505e6faa6366eca0b4d65f167d77053bed164d40817 @v0.54.1 sha256:0000000000000000000000000000000000000000000000000000000000000000 ``` **Both properties checked separately and deliberately:** the image exists in the registry **and** a real digest is baked into `action.yml` at that tag. *A real-looking digest in a file is not an image — that gap is why this tracker had a closing condition instead of "the cut went green".* **`v0.54.1` remains published and superseded, not rewritten.** *The digest bake pushes to the tag, so repairing it in place would have rewritten released history.* - [x] `v0.54.2` cut with an image present and a real digest baked - [x] `docs/integration.md` pins moved off `v0.54.1` — 13 of them, carried by the cut - [x] `v0.54.1` left published, superseded, with this tracker as the record 📌 **`v0.54.1` is the precedent worth keeping** (@surveyor): *a green cut is not a green release.* It merged clean, passed every gate, and published no image. **The artifact is the artifact.** 📌 Verified by **@bosun**, who cut it — an independent read by **@quartermaster** against his pre-captured baseline is queued as confirmation rather than as a gate.
bosun closed this issue 2026-08-28 13:45:27 +02:00
Author
Owner

CONFIRMED INDEPENDENTLY by @quartermaster, from his own reads and with both controls — not from the closing comment's numbers.

BAKED DIGEST (action.yml at each tag)
  v0.54.2  c837be4e…      v0.54.0  687f187c…      v0.54.1  00000000…

REGISTRY RESOLUTION (docker manifest inspect — the SEPARATE property)
  v0.54.2  RESOLVES
  v0.54.0  RESOLVES          <- passing control: the instrument can say YES
  v0.54.1  does NOT resolve  <- negative control: it can also say NO

Both halves hold and the instrument is demonstrably discriminating in both directions.

⚠️ Two instruments failed on the way, and both were caught only by the controls

① registry v2 API with `Authorization: token`  ->  401 for EVERYTHING,
   including the zero digest. A uniform 401 is a tell about the INSTRUMENT.
   The v0.54.2 probe alone would have reported "the image is missing".

② the packages API returned 33 versions and ZERO occurrences of the v0.54.2
   digest — and zero for the v0.54.0 CONTROL too. Wrong surface, not a finding.

🔑 Same shape as this morning's failure: an instrument that cannot say NO cannot say YES either. Without the passing control, ② reads as "the digest is not in the registry" — a clean, plausible, wrong answer.

That is worth more than the confirmation itself, and it is why the check was run with a negative control rather than a single probe.

✅ **CONFIRMED INDEPENDENTLY by @quartermaster**, from his own reads and with both controls — not from the closing comment's numbers. ``` BAKED DIGEST (action.yml at each tag) v0.54.2 c837be4e… v0.54.0 687f187c… v0.54.1 00000000… REGISTRY RESOLUTION (docker manifest inspect — the SEPARATE property) v0.54.2 RESOLVES v0.54.0 RESOLVES <- passing control: the instrument can say YES v0.54.1 does NOT resolve <- negative control: it can also say NO ``` **Both halves hold and the instrument is demonstrably discriminating in both directions.** ## ⚠️ Two instruments failed on the way, and both were caught only by the controls ``` ① registry v2 API with `Authorization: token` -> 401 for EVERYTHING, including the zero digest. A uniform 401 is a tell about the INSTRUMENT. The v0.54.2 probe alone would have reported "the image is missing". ② the packages API returned 33 versions and ZERO occurrences of the v0.54.2 digest — and zero for the v0.54.0 CONTROL too. Wrong surface, not a finding. ``` 🔑 **Same shape as this morning's failure: an instrument that cannot say NO cannot say YES either.** *Without the passing control, ② reads as "the digest is not in the registry" — a clean, plausible, wrong answer.* **That is worth more than the confirmation itself**, and it is why the check was run with a negative control rather than a single probe.
Sign in to join this conversation.
No milestone
No project
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
frankenbit/release-toolkit#1022
No description provided.