quiz-generation (final book-pipeline stage) keeps tripping its rolling update with No such image on freshly-pushed digests, then self-heals a minute or two later. Observed 3 different digests fail then recover in ~10 min (2026-07-12 ~18:18–18:31 UTC):
Rejected ...@sha256:5f25f967... "No such image" (~18:03)
Rejected ...@sha256:17d60d8b... "No such image" (~18:23) -> later converged 1/1
Rejected ...@sha256:fc0b0731... "No such image" (~18:29) -> later converged 1/1
Current state: service is 1/1 Running on the fc0b0731 digest, and docker manifest inspect now finds it in the registry — but UpdateStatus: paused — update paused due to failure or early termination of task ....
Root cause (very likely): CI updates the service spec BEFORE the image push completes
The pattern — spec pinned to @sha256:X → swarm rejects with No such image: ...@sha256:X → the same digest becomes pullable a bit later and the task converges — is the classic push/deploy ordering race: the pipeline runs docker service update --image ...@sha256:X (or stack deploy) while the docker push of that digest is still in flight / not yet committed on the registry. Swarm can't pull it, tasks get Rejected, and after enough failures the rolling update flips to paused (which can leave the service stuck on a bad revision if the push never lands).
Impact
Transient outages of the final pipeline stage (quiz generation) on every deploy — books completing during the window don't get their quiz created until it converges.
UpdateStatus=paused means a genuinely-bad push (digest that never lands) would leave quiz-generation stuck rather than rolling forward — a deploy that silently half-applies.
Noise: repeated No such image reject loops make real failures harder to spot.
Fix options
Order the pipeline correctly — ensure docker push (all layers + manifest) fully completes and the digest is verifiable in the registry before the service update/stack deploy step. A simple gate: after push, docker manifest inspect <registry>/<repo>@sha256:<digest> must succeed before updating the service.
Deploy by immutable tag, then update — push a unique tag (e.g. commit SHA), verify it's pullable, then update the service to that tag/digest. Avoids racing :latest.
Registry write consistency — if the registry (Gitea) returns success on push before the manifest is queryable, add a short verify-with-retry after push.
Consider --update-failure-action rollback (or fixing the paused state handling) so a bad digest rolls back cleanly instead of parking paused.
Repro / evidence
docker service ps spikersoft-quiz-generation_quiz-generation → interleaved Rejected "No such image: ...@sha256:..." then Running across 3 digests.
docker service inspect ... --format '{{.UpdateStatus.State}} {{.UpdateStatus.Message}}' → paused — update paused due to failure or early termination of task ....
docker manifest inspect .../spikersoft-quiz-generation:latest → EXISTS now (i.e. the digest that was "No such image" minutes ago is present now → timing, not a permanently-missing image).
Related
#510 — resilience exploration, item C (verify pushed digest is pullable before updating the spec). This ticket is the concrete, actionable instance of that item.
#508 — image-description phantom deploy (same family: service spec references an image the registry doesn't have at update time).
Suggested test / guard
CI: after build+push, a mandatory docker manifest inspect <repo>@<digest> (with brief retry) gate that must pass before the deploy step. That single check would have prevented all three reject loops above.
## Summary
`quiz-generation` (final book-pipeline stage) keeps tripping its rolling update with `No such image` on freshly-pushed digests, then self-heals a minute or two later. Observed **3 different digests fail then recover** in ~10 min (2026-07-12 ~18:18–18:31 UTC):
```
Rejected ...@sha256:5f25f967... "No such image" (~18:03)
Rejected ...@sha256:17d60d8b... "No such image" (~18:23) -> later converged 1/1
Rejected ...@sha256:fc0b0731... "No such image" (~18:29) -> later converged 1/1
```
Current state: service is `1/1 Running` on the fc0b0731 digest, and `docker manifest inspect` now finds it in the registry — but `UpdateStatus: paused — update paused due to failure or early termination of task ...`.
## Root cause (very likely): CI updates the service spec BEFORE the image push completes
The pattern — spec pinned to `@sha256:X` → swarm rejects with `No such image: ...@sha256:X` → the *same* digest becomes pullable a bit later and the task converges — is the classic **push/deploy ordering race**: the pipeline runs `docker service update --image ...@sha256:X` (or `stack deploy`) while the `docker push` of that digest is still in flight / not yet committed on the registry. Swarm can't pull it, tasks get Rejected, and after enough failures the rolling update flips to `paused` (which can leave the service stuck on a bad revision if the push never lands).
## Impact
- Transient outages of the **final pipeline stage** (quiz generation) on every deploy — books completing during the window don't get their quiz created until it converges.
- `UpdateStatus=paused` means a genuinely-bad push (digest that never lands) would leave quiz-generation stuck rather than rolling forward — a deploy that silently half-applies.
- Noise: repeated `No such image` reject loops make real failures harder to spot.
## Fix options
1. **Order the pipeline correctly** — ensure `docker push` (all layers + manifest) fully completes and the digest is verifiable in the registry *before* the `service update`/`stack deploy` step. A simple gate: after push, `docker manifest inspect <registry>/<repo>@sha256:<digest>` must succeed before updating the service.
2. **Deploy by immutable tag, then update** — push a unique tag (e.g. commit SHA), verify it's pullable, then update the service to that tag/digest. Avoids racing `:latest`.
3. **Registry write consistency** — if the registry (Gitea) returns success on push before the manifest is queryable, add a short verify-with-retry after push.
4. Consider `--update-failure-action rollback` (or fixing the paused state handling) so a bad digest rolls back cleanly instead of parking `paused`.
## Repro / evidence
- `docker service ps spikersoft-quiz-generation_quiz-generation` → interleaved `Rejected "No such image: ...@sha256:..."` then `Running` across 3 digests.
- `docker service inspect ... --format '{{.UpdateStatus.State}} {{.UpdateStatus.Message}}'` → `paused — update paused due to failure or early termination of task ...`.
- `docker manifest inspect .../spikersoft-quiz-generation:latest` → EXISTS now (i.e. the digest that was "No such image" minutes ago is present now → timing, not a permanently-missing image).
## Related
- #510 — resilience exploration, **item C** (verify pushed digest is pullable before updating the spec). This ticket is the concrete, actionable instance of that item.
- #508 — image-description phantom deploy (same family: service spec references an image the registry doesn't have at update time).
## Suggested test / guard
- CI: after build+push, a mandatory `docker manifest inspect <repo>@<digest>` (with brief retry) gate that must pass before the deploy step. That single check would have prevented all three reject loops above.
Both guards up: backend PR #238 (post-push docker manifest inspect retry gate — the deploy step can no longer start until the digest is actually servable; a push that never lands fails the BUILD) and infra PR #44 (update_config: failure_action: rollback, order: start-first — residual bad rollouts self-revert instead of parking paused). Scoped to quiz-generation per this ticket; the same 8-line gate is copy-paste for the other ~25 workflows — recommend a follow-up sweep (happy to do it as one PR on request). Timing note: tonight's three occurrences coincided with the registry's disk/MinIO stress — exactly when push-success ≠ immediately-servable, which is why the gate polls the registry rather than trusting the push exit code.
Both guards up: **backend PR #238** (post-push `docker manifest inspect` retry gate — the deploy step can no longer start until the digest is actually servable; a push that never lands fails the BUILD) and **infra PR #44** (`update_config: failure_action: rollback, order: start-first` — residual bad rollouts self-revert instead of parking paused). Scoped to quiz-generation per this ticket; the same 8-line gate is copy-paste for the other ~25 workflows — recommend a follow-up sweep (happy to do it as one PR on request). Timing note: tonight's three occurrences coincided with the registry's disk/MinIO stress — exactly when push-success ≠ immediately-servable, which is why the gate polls the registry rather than trusting the push exit code.
Fleet sweep up: PR #239 gates all 28 image-publishing workflows (also re-lands quiz-generation's gate — #238 had merged with only the accidental empty commit). With infra #44's rollback policy merged, both halves of this ticket are done pending the sweep merge; #510's item C is covered fleet-wide by the same PR. Will close on merge.
Fleet sweep up: **PR #239** gates all 28 image-publishing workflows (also re-lands quiz-generation's gate — #238 had merged with only the accidental empty commit). With infra #44's rollback policy merged, both halves of this ticket are done pending the sweep merge; #510's item C is covered fleet-wide by the same PR. Will close on merge.
Closing with evidence: backend PR #239 merged (deploy-race gate — post-push docker manifest inspect retry, 12×5s — now guards all 28 image-publishing workflows incl. quiz-generation) and infra PR #44 merged (update_config: failure_action: rollback, order: start-first). Both halves of the fix are on master; subsequent deploys have not reproduced the No such image reject loop. Fleet-wide coverage also satisfies #510 item C.
Closing with evidence: **backend PR #239 merged** (deploy-race gate — post-push `docker manifest inspect` retry, 12×5s — now guards all 28 image-publishing workflows incl. quiz-generation) and **infra PR #44 merged** (`update_config: failure_action: rollback, order: start-first`). Both halves of the fix are on master; subsequent deploys have not reproduced the `No such image` reject loop. Fleet-wide coverage also satisfies #510 item C.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
quiz-generation(final book-pipeline stage) keeps tripping its rolling update withNo such imageon freshly-pushed digests, then self-heals a minute or two later. Observed 3 different digests fail then recover in ~10 min (2026-07-12 ~18:18–18:31 UTC):Current state: service is
1/1 Runningon the fc0b0731 digest, anddocker manifest inspectnow finds it in the registry — butUpdateStatus: paused — update paused due to failure or early termination of task ....Root cause (very likely): CI updates the service spec BEFORE the image push completes
The pattern — spec pinned to
@sha256:X→ swarm rejects withNo such image: ...@sha256:X→ the same digest becomes pullable a bit later and the task converges — is the classic push/deploy ordering race: the pipeline runsdocker service update --image ...@sha256:X(orstack deploy) while thedocker pushof that digest is still in flight / not yet committed on the registry. Swarm can't pull it, tasks get Rejected, and after enough failures the rolling update flips topaused(which can leave the service stuck on a bad revision if the push never lands).Impact
UpdateStatus=pausedmeans a genuinely-bad push (digest that never lands) would leave quiz-generation stuck rather than rolling forward — a deploy that silently half-applies.No such imagereject loops make real failures harder to spot.Fix options
docker push(all layers + manifest) fully completes and the digest is verifiable in the registry before theservice update/stack deploystep. A simple gate: after push,docker manifest inspect <registry>/<repo>@sha256:<digest>must succeed before updating the service.:latest.--update-failure-action rollback(or fixing the paused state handling) so a bad digest rolls back cleanly instead of parkingpaused.Repro / evidence
docker service ps spikersoft-quiz-generation_quiz-generation→ interleavedRejected "No such image: ...@sha256:..."thenRunningacross 3 digests.docker service inspect ... --format '{{.UpdateStatus.State}} {{.UpdateStatus.Message}}'→paused — update paused due to failure or early termination of task ....docker manifest inspect .../spikersoft-quiz-generation:latest→ EXISTS now (i.e. the digest that was "No such image" minutes ago is present now → timing, not a permanently-missing image).Related
Suggested test / guard
docker manifest inspect <repo>@<digest>(with brief retry) gate that must pass before the deploy step. That single check would have prevented all three reject loops above.Both guards up: backend PR #238 (post-push
docker manifest inspectretry gate — the deploy step can no longer start until the digest is actually servable; a push that never lands fails the BUILD) and infra PR #44 (update_config: failure_action: rollback, order: start-first— residual bad rollouts self-revert instead of parking paused). Scoped to quiz-generation per this ticket; the same 8-line gate is copy-paste for the other ~25 workflows — recommend a follow-up sweep (happy to do it as one PR on request). Timing note: tonight's three occurrences coincided with the registry's disk/MinIO stress — exactly when push-success ≠ immediately-servable, which is why the gate polls the registry rather than trusting the push exit code.Fleet sweep up: PR #239 gates all 28 image-publishing workflows (also re-lands quiz-generation's gate — #238 had merged with only the accidental empty commit). With infra #44's rollback policy merged, both halves of this ticket are done pending the sweep merge; #510's item C is covered fleet-wide by the same PR. Will close on merge.
Closing with evidence: backend PR #239 merged (deploy-race gate — post-push
docker manifest inspectretry, 12×5s — now guards all 28 image-publishing workflows incl. quiz-generation) and infra PR #44 merged (update_config: failure_action: rollback, order: start-first). Both halves of the fix are on master; subsequent deploys have not reproduced theNo such imagereject loop. Fleet-wide coverage also satisfies #510 item C.