Fixes the mirror workflow that failed in run 5156 (PUT .../manifests/<tag>-staging: 404).
Root cause (reproduced on real Gitea with scratch zz-compat-* packages). Gitea rejects an index PUT unless every child manifest is already stored as a by-digest version (MANIFEST_BLOB_UNKNOWN, shown as 404). buildx <= 0.20.x, including the runner's distro buildx 0.15.1, pushes the children of a copied index to the tag path. That works on registry:2, not on Gitea, and it leaves a stray single-manifest <tag>-staging version. buildx >= 0.24 pushes children by digest and works.
Gitea facts measured. It accepts OCI indexes, Docker manifest lists, unknown/unknown attestation children, and 14-16 children. It preserves the index digest byte-for-byte, so the digest-equality assertion still holds. docker pull --platform linux/arm64 selects the right child.
Changes
Workflow: install a sha256-verified pinned buildx v0.28.0 (imagetools needs no builder, so setup-buildx-action is dropped) and pass it as $BUILDX.
mirror-index-copy.sh: MIN_BUILDX guard (default 0.24.0) refuses an old buildx before any registry traffic, so no stray version is created. The digest is now the sha256 of the raw index bytes, because buildx 0.28 ignores inspect --format here.
Test: new attestation-children case and old-buildx guard case (10/10 pass against a local registry:2).
Proven on real Gitea (scratch names, since deleted): the full script with buildx 0.28.0 copied the real python:3.12-slim index (16 children), replaced a pre-existing single-manifest tag, and passed both assertions with digest sha256:5e2bdb1b... preserved. A second run was idempotent.
Merge note: merging to main triggers the HMB image build on any push. Use [skip ci] in the merge title.
Not verified: a live run in Gitea Actions (the runner cannot reach Docker Hub from here; I used the same index from a local registry), and the pinned-buildx download step inside the act runner.
Fixes the mirror workflow that failed in run 5156 (`PUT .../manifests/<tag>-staging: 404`).
**Root cause (reproduced on real Gitea with scratch `zz-compat-*` packages).** Gitea rejects an index PUT unless every child manifest is already stored as a by-digest version (`MANIFEST_BLOB_UNKNOWN`, shown as 404). buildx <= 0.20.x, including the runner's distro buildx 0.15.1, pushes the children of a copied index to the tag path. That works on registry:2, not on Gitea, and it leaves a stray single-manifest `<tag>-staging` version. buildx >= 0.24 pushes children by digest and works.
**Gitea facts measured.** It accepts OCI indexes, Docker manifest lists, `unknown/unknown` attestation children, and 14-16 children. It preserves the index digest byte-for-byte, so the digest-equality assertion still holds. `docker pull --platform linux/arm64` selects the right child.
**Changes**
- Workflow: install a sha256-verified pinned buildx v0.28.0 (imagetools needs no builder, so setup-buildx-action is dropped) and pass it as `$BUILDX`.
- `mirror-index-copy.sh`: `MIN_BUILDX` guard (default 0.24.0) refuses an old buildx before any registry traffic, so no stray version is created. The digest is now the sha256 of the raw index bytes, because buildx 0.28 ignores `inspect --format` here.
- Test: new attestation-children case and old-buildx guard case (10/10 pass against a local registry:2).
**Proven on real Gitea** (scratch names, since deleted): the full script with buildx 0.28.0 copied the real python:3.12-slim index (16 children), replaced a pre-existing single-manifest tag, and passed both assertions with digest sha256:5e2bdb1b... preserved. A second run was idempotent.
**Merge note:** merging to main triggers the HMB image build on any push. Use `[skip ci]` in the merge title.
Not verified: a live run in Gitea Actions (the runner cannot reach Docker Hub from here; I used the same index from a local registry), and the pinned-buildx download step inside the act runner.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_0185ukXjXyosSkunDsdWz3mb
Run 5156 failed copying python:3.12-slim / golang:1.25 into Gitea:
`PUT .../manifests/<tag>-staging: 404`, leaving a stray single-manifest
<tag>-staging version. Reproduced on real Gitea with scratch packages:
buildx <= 0.20.x (runner distro buildx is 0.15.1) pushes the children of a
copied index to the TAG path; Gitea only records manifests PUT by digest, so
the index PUT is rejected (MANIFEST_BLOB_UNKNOWN -> 404). buildx >= 0.24
pushes children by digest and works; Gitea then preserves the index digest, so
the digest-equality assertion stays.
- workflow: install a sha256-verified pinned buildx (v0.28.0) instead of
setup-buildx-action (imagetools needs no builder) and pass it as $BUILDX
- mirror-index-copy.sh: BUILDX/MIN_BUILDX guard refuses buildx < 0.24 before
any registry traffic; digest computed as sha256 of the raw index bytes
(buildx 0.28 ignores `inspect --format` here)
- test: attestation-children index and old-buildx guard cases
Merge with [skip ci] in the merge title: any push to main triggers the HMB
image build.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0185ukXjXyosSkunDsdWz3mb
jknapp
merged commit 48edc09e8d into main2026-11-12 01:03:16 +00:00
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.
Fixes the mirror workflow that failed in run 5156 (
PUT .../manifests/<tag>-staging: 404).Root cause (reproduced on real Gitea with scratch
zz-compat-*packages). Gitea rejects an index PUT unless every child manifest is already stored as a by-digest version (MANIFEST_BLOB_UNKNOWN, shown as 404). buildx <= 0.20.x, including the runner's distro buildx 0.15.1, pushes the children of a copied index to the tag path. That works on registry:2, not on Gitea, and it leaves a stray single-manifest<tag>-stagingversion. buildx >= 0.24 pushes children by digest and works.Gitea facts measured. It accepts OCI indexes, Docker manifest lists,
unknown/unknownattestation children, and 14-16 children. It preserves the index digest byte-for-byte, so the digest-equality assertion still holds.docker pull --platform linux/arm64selects the right child.Changes
$BUILDX.mirror-index-copy.sh:MIN_BUILDXguard (default 0.24.0) refuses an old buildx before any registry traffic, so no stray version is created. The digest is now the sha256 of the raw index bytes, because buildx 0.28 ignoresinspect --formathere.Proven on real Gitea (scratch names, since deleted): the full script with buildx 0.28.0 copied the real python:3.12-slim index (16 children), replaced a pre-existing single-manifest tag, and passed both assertions with digest sha256:5e2bdb1b... preserved. A second run was idempotent.
Merge note: merging to main triggers the HMB image build on any push. Use
[skip ci]in the merge title.Not verified: a live run in Gitea Actions (the runner cannot reach Docker Hub from here; I used the same index from a local registry), and the pinned-buildx download step inside the act runner.
🤖 Generated with Claude Code
https://claude.ai/code/session_0185ukXjXyosSkunDsdWz3mb