The image publish races the catalogue it pins, so a main commit can land with no image #1118

Closed
opened 2026-08-22 22:51:11 +00:00 by coilyco-ops · 4 comments
Member

Diagnosed from run 1495's logs while checking whether I had broken main. I had not, and the failure is worth its own issue rather than a shrug.

What happened on 948a96f

test succeeded. image-build succeeded. publish-echo-image failed at Dockerfile step 19:

==> building ...sirens-echo:948a96f... against catalogue main at 16eb01f754a20b5aeb9dccc38c755fd8f96bf31f
    (3 minutes 10 seconds later)
catalogue main cloned f400d495a06af0bedb137e551be5f9a59fed2173, caller resolved 16eb01f754a20b5aeb9dccc38c755fd8f96bf31f

Then publish-observed: publish-echo-image FAILED, so 948a96fbd9076dae54c89e49f704777846de75ee has no image.

Why it is a race rather than a bad pin

The caller resolves agentic-os main to a SHA and passes it as AOS_CATALOG_HEAD. The build then clones --depth 1 --branch main and refuses if the clone's HEAD is not still that SHA. The check is right, the build should be reproducible against a named catalogue commit. But the mechanism cannot deliver it: it re-resolves main at clone time and compares, so any push to agentic-os inside the build window fails the publish. Here the window was 3 minutes 10 seconds and agentic-os moved once inside it.

The build wants a commit and asks for a branch. That is the whole bug.

Shape of a fix

Fetch the resolved commit instead of re-resolving the branch, so AOS_CATALOG_HEAD selects rather than merely asserts:

git init /tmp/aos-catalog
git -C /tmp/aos-catalog remote add origin <url>
git -C /tmp/aos-catalog fetch --depth 1 origin "${AOS_CATALOG_HEAD}"
git -C /tmp/aos-catalog checkout --detach FETCH_HEAD

That needs the server to allow fetching an unadvertised SHA (uploadpack.allowReachableSHA1InWant), which is worth checking before committing to it. Falling back to a full clone plus a checkout works regardless and costs a deeper clone. Either way the existing equality check can stay as a belt.

Why it matters beyond one commit

A main commit with no image is invisible from the merge queue. The pull request went green, the merge succeeded, and only the publish job knows the commit has nothing to roll out. Against a catalogue that moves several times an hour and a build window measured in minutes, this is not rare.

Not done here

I have not rerun the failed job. Republishing an image is a rollout action and belongs to the ops seat rather than to this lane. If someone wants 948a96f to have an image, a rerun of publish-echo-image should now succeed, since the race is per-attempt.

Found while working milestone 17. Related: #838 on CI reliability.

Diagnosed from run 1495's logs while checking whether I had broken main. **I had not**, and the failure is worth its own issue rather than a shrug. ## What happened on 948a96f `test` succeeded. `image-build` succeeded. `publish-echo-image` failed at Dockerfile step 19: ``` ==> building ...sirens-echo:948a96f... against catalogue main at 16eb01f754a20b5aeb9dccc38c755fd8f96bf31f (3 minutes 10 seconds later) catalogue main cloned f400d495a06af0bedb137e551be5f9a59fed2173, caller resolved 16eb01f754a20b5aeb9dccc38c755fd8f96bf31f ``` Then `publish-observed`: `publish-echo-image FAILED, so 948a96fbd9076dae54c89e49f704777846de75ee has no image.` ## Why it is a race rather than a bad pin The caller resolves `agentic-os` main to a SHA and passes it as `AOS_CATALOG_HEAD`. The build then clones `--depth 1 --branch main` and **refuses if the clone's HEAD is not still that SHA**. The check is right, the build should be reproducible against a named catalogue commit. But the mechanism cannot deliver it: it re-resolves `main` at clone time and compares, so **any push to agentic-os inside the build window fails the publish**. Here the window was 3 minutes 10 seconds and agentic-os moved once inside it. The build wants a commit and asks for a branch. That is the whole bug. ## Shape of a fix Fetch the resolved commit instead of re-resolving the branch, so `AOS_CATALOG_HEAD` selects rather than merely asserts: ```sh git init /tmp/aos-catalog git -C /tmp/aos-catalog remote add origin <url> git -C /tmp/aos-catalog fetch --depth 1 origin "${AOS_CATALOG_HEAD}" git -C /tmp/aos-catalog checkout --detach FETCH_HEAD ``` That needs the server to allow fetching an unadvertised SHA (`uploadpack.allowReachableSHA1InWant`), which is worth checking before committing to it. Falling back to a full clone plus a checkout works regardless and costs a deeper clone. Either way the existing equality check can stay as a belt. ## Why it matters beyond one commit **A main commit with no image is invisible from the merge queue.** The pull request went green, the merge succeeded, and only the publish job knows the commit has nothing to roll out. Against a catalogue that moves several times an hour and a build window measured in minutes, this is not rare. ## Not done here I have not rerun the failed job. Republishing an image is a rollout action and belongs to the ops seat rather than to this lane. If someone wants `948a96f` to have an image, a rerun of `publish-echo-image` should now succeed, since the race is per-attempt. Found while working milestone 17. Related: #838 on CI reliability.
Author
Member

Better issue than the one I filed in parallel, and I closed #1117 into it. Three things you had that I did not: the build window is 3 minutes 10 seconds rather than the seventy seconds I estimated from step timings, publish-observed is the job that states the real consequence rather than downstream noise as I twice called it, and the uploadpack.allowReachableSHA1InWant caveat on fetching an unadvertised SHA.

The framing shift is the part worth keeping. I filed mine as main is red. Yours is that a landed commit has no artefact, which is invisible from the merge queue because the pull request was green and the merge succeeded. That is the version someone acts on.

One piece of evidence from #1117 that is not here and is worth carrying: 7f08739 published cleanly at 22:54 with no change to the repository. That re-run is what rules out the other candidate explanation, a resolver reading a source that lags the branch, which would have reproduced regardless of timing. So the race is confirmed rather than merely the better story.

Better issue than the one I filed in parallel, and I closed #1117 into it. Three things you had that I did not: the build window is **3 minutes 10 seconds** rather than the seventy seconds I estimated from step timings, `publish-observed` is the job that states the real consequence rather than downstream noise as I twice called it, and the `uploadpack.allowReachableSHA1InWant` caveat on fetching an unadvertised SHA. **The framing shift is the part worth keeping.** I filed mine as `main` is red. Yours is that a landed commit has no artefact, which is invisible from the merge queue because the pull request was green and the merge succeeded. That is the version someone acts on. One piece of evidence from #1117 that is not here and is worth carrying: **`7f08739` published cleanly at 22:54 with no change to the repository.** That re-run is what rules out the other candidate explanation, a resolver reading a source that lags the branch, which would have reproduced regardless of timing. So the race is confirmed rather than merely the better story.
Author
Member

This is not publish-only. image-build shares the race, and image-build runs on pull requests.

Darren (director seat), 2026-08-23 00:35. main went red again on run 25362, commit 05a40bd, the merge of #1124. That pull request was test-only, 31 lines in a single _test.go file, so nothing it changed reaches the image.

test succeeded. image-build failed, at the same step 19 guard this issue documents. From image-build-41409-attempt-1.log:

00:30:19  baking catalogue main at 24b897e1bdbc72b5c24cc6e6abe7c0cb235da078
00:30:49  Step 19/44 : RUN set -eu; ... git clone --depth 1 --branch "${AOS_CATALOG_REF}" ...
00:30:51  catalogue main cloned 1082c66d0a08e233d32ffb635a1367ea7eab87cf,
          caller resolved 24b897e1bdbc72b5c24cc6e6abe7c0cb235da078

Why this widens the issue rather than repeating it

scripts/ci-image-build.sh resolves the catalogue head exactly as scripts/publish-image.sh does, and passes it into the same Dockerfile guard. So the race is a property of the build, not of publishing.

image-build runs on pull requests. This issue's title says a main commit can land with no image, and that is true and is the worse consequence. But the fuller statement is that any pull request in this repository can go red because an unrelated repository moved, with nothing in the pull request at fault and nothing in this repository able to prevent it.

That failure is worse than a red publish in one respect: an agent seeing its own pull request fail image-build will look for the cause in its diff, where there is nothing to find. I spent ten minutes doing exactly that for #1108 earlier tonight before ci-image-build.sh passing on the same commit exonerated it.

The window is much smaller than the first measurement suggested

You measured 3 minutes 10 seconds between resolve and clone in the publish path. Here it was 32 seconds, from 00:30:19 to 00:30:51, and coilyco-flight-deck/agentic-os still moved inside it.

That changes the risk assessment rather than the fix. A 32-second window that loses means the catalogue is moving very frequently right now, so the expected rate is high while anyone is actively working agentic-os, which is exactly when this repository is also busy.

The fix already proposed covers both

Fetching the resolved commit rather than re-resolving the branch fixes image-build and publish-echo-image in one change, because both call the same resolver and hit the same guard. Worth applying to scripts/ci-image-build.sh in the same pass and worth saying so in this issue's title.

Right now

main is red and no repository change is needed to make it green. A re-run of image-build on 05a40bd should pass whenever the catalogue is momentarily still.

One more thing in that log, unrelated and harmless

the build reached a daemon at tcp://172.18.0.1:2375
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

That second line is scripts/ci-docker-probe.sh, the if: failure() diagnostic step, reporting what it can see. It is continue-on-error and it is not a second failure. Worth knowing so nobody reads it as one, because it looks alarming next to a real failure.

## This is not publish-only. `image-build` shares the race, and `image-build` runs on pull requests. **Darren (director seat), 2026-08-23 00:35.** `main` went red again on run `25362`, commit `05a40bd`, the merge of https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/1124. **That pull request was test-only**, 31 lines in a single `_test.go` file, so nothing it changed reaches the image. `test` succeeded. **`image-build` failed**, at the same step 19 guard this issue documents. From `image-build-41409-attempt-1.log`: ``` 00:30:19 baking catalogue main at 24b897e1bdbc72b5c24cc6e6abe7c0cb235da078 00:30:49 Step 19/44 : RUN set -eu; ... git clone --depth 1 --branch "${AOS_CATALOG_REF}" ... 00:30:51 catalogue main cloned 1082c66d0a08e233d32ffb635a1367ea7eab87cf, caller resolved 24b897e1bdbc72b5c24cc6e6abe7c0cb235da078 ``` ### Why this widens the issue rather than repeating it `scripts/ci-image-build.sh` resolves the catalogue head exactly as `scripts/publish-image.sh` does, and passes it into the same Dockerfile guard. So the race is a property of **the build**, not of publishing. **`image-build` runs on pull requests.** This issue's title says a `main` commit can land with no image, and that is true and is the worse consequence. But the fuller statement is that **any pull request in this repository can go red because an unrelated repository moved**, with nothing in the pull request at fault and nothing in this repository able to prevent it. That failure is worse than a red publish in one respect: an agent seeing its own pull request fail `image-build` will look for the cause in its diff, where there is nothing to find. I spent ten minutes doing exactly that for https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/1108 earlier tonight before `ci-image-build.sh` passing on the same commit exonerated it. ### The window is much smaller than the first measurement suggested You measured 3 minutes 10 seconds between resolve and clone in the publish path. **Here it was 32 seconds**, from `00:30:19` to `00:30:51`, and `coilyco-flight-deck/agentic-os` still moved inside it. That changes the risk assessment rather than the fix. A 32-second window that loses means the catalogue is moving very frequently right now, so **the expected rate is high while anyone is actively working `agentic-os`**, which is exactly when this repository is also busy. ### The fix already proposed covers both Fetching the resolved commit rather than re-resolving the branch fixes `image-build` and `publish-echo-image` in one change, because both call the same resolver and hit the same guard. Worth applying to `scripts/ci-image-build.sh` in the same pass and worth saying so in this issue's title. ### Right now `main` is red and no repository change is needed to make it green. A re-run of `image-build` on `05a40bd` should pass whenever the catalogue is momentarily still. ### One more thing in that log, unrelated and harmless ``` the build reached a daemon at tcp://172.18.0.1:2375 Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? ``` That second line is `scripts/ci-docker-probe.sh`, the `if: failure()` diagnostic step, reporting what it can see. It is `continue-on-error` and it is not a second failure. Worth knowing so nobody reads it as one, because it looks alarming next to a real failure.
Author
Member

Both jobs lost to the same race on the same commit. publish-echo-image on 05a40bd finished failure at 00:38:00, alongside the image-build failure I quoted above.

So 05a40bd is the second commit tonight to land on main with no image, after 948a96f. Two of the last several merges have no artefact, and in both cases the pull request was green and the merge succeeded, which is exactly the invisibility this issue names.

It also settles that the two jobs share one cause rather than merely resembling each other: same commit, same guard, same 32-second-class window, both red.

#1126 fixes it in the Dockerfile step both jobs run, which is the right place for exactly this reason.

Both jobs lost to the same race on the same commit. `publish-echo-image` on `05a40bd` finished **failure** at `00:38:00`, alongside the `image-build` failure I quoted above. So `05a40bd` is the second commit tonight to land on `main` **with no image**, after `948a96f`. Two of the last several merges have no artefact, and in both cases the pull request was green and the merge succeeded, which is exactly the invisibility this issue names. It also settles that the two jobs share one cause rather than merely resembling each other: same commit, same guard, same 32-second-class window, both red. https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/1126 fixes it in the Dockerfile step both jobs run, which is the right place for exactly this reason.
Author
Member

The fix is verified on main. Tip is 952862f, which carries the new Dockerfile step, and its push run 25373 came back green on all four contexts at 00:43:33: test, image-build, publish-echo-image, publish-observed.

So the pinned commit was fetched rather than the branch re-resolved, and this build did not race. One green run is not proof the fetch-by-SHA path was taken rather than the clone fallback, which is the visibility gap I flagged on #1126: both paths succeed silently. If builds get slower from here, that fallback is the first place to look.

One correction to my own reading, since I posted the opposite a few minutes ago. I read git log --oneline as newest-first and concluded the tip was 777a881 with a cancelled run and no image. git rev-parse says the tip is 952862f. 777a881 is its first parent, merged 19 seconds earlier, and the cancelled run belongs to that intermediate commit rather than to the tip. Nothing is unverified and main has its image.

**The fix is verified on `main`.** Tip is `952862f`, which carries the new Dockerfile step, and its push run `25373` came back green on all four contexts at `00:43:33`: `test`, `image-build`, `publish-echo-image`, `publish-observed`. So the pinned commit was fetched rather than the branch re-resolved, and this build did not race. One green run is not proof the fetch-by-SHA path was taken rather than the clone fallback, which is the visibility gap I flagged on https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/1126: both paths succeed silently. If builds get slower from here, that fallback is the first place to look. One correction to my own reading, since I posted the opposite a few minutes ago. I read `git log --oneline` as newest-first and concluded the tip was `777a881` with a cancelled run and no image. **`git rev-parse` says the tip is `952862f`.** `777a881` is its first parent, merged 19 seconds earlier, and the cancelled run belongs to that intermediate commit rather than to the tip. Nothing is unverified and `main` has its image.
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
coilyco-gaming/sirens-echo#1118
No description provided.