Follow-up to #244 and #245. Two artefacts, opposite answers.
Releases stay — all of them
They carry no assets (v2026.9.2-pr244 has zero; the image lives in GHCR), so a release is a tag, a title and generated notes. And there is no CHANGELOG file in this repository, which makes those generated notes the only changelog the project has.
Deleting one destroys history that cannot be reconstructed and saves nothing. Rollback would survive — deleting a release does not delete its tag — but the record of what was in it would not.
Images get pruned
A multi-arch build a week, and the by-digest push in publish.yml leaves two untagged per-architecture manifests behind each time on top of the tagged index. Ten tagged versions kept — roughly a quarter of a year, and far more than anything anyone rolls back to.
The trap
The obvious tool is actions/delete-package-versions with delete-only-untagged-versions: true. It will delete the per-architecture manifests that a multi-arch tag points at, because they are untagged by design.
Nothing appears to break. The tag still resolves. Pulls just start failing for one architecture, at some later date, for a reason nobody connects to a cleanup job that ran weeks earlier.
dataaxiom/ghcr-cleanup-action understands manifest lists and will not orphan a retained index. On top of that:
validate: true re-checks every remaining multi-arch manifest against the registry after the sweep — the step that would catch the footgun rather than leave it to be discovered on docker pull
exclude-tags: latest takes latest out of consideration under any counting
delete-partial-images / delete-orphaned-images sweep the wreckage of a half-failed run
Pinned to a commit
@d52806a0 (v1.2.2), not @v1. This action is handed packages: write and its entire purpose is deletion; a tag repointed upstream — by compromise or mistake — is not a risk worth carrying for the convenience of a floating major.
Its own file, on purpose
cleanup.yml is workflow_callandworkflow_dispatch, with dry_run defaulting to true on manual runs. That means the deletion list can be inspected without rebuilding and re-pushing an image to trigger it.
Testing
actionlint clean across all three workflows. After merge I'll dispatch it with dry_run: true and read exactly what it proposes to delete before anything runs for real — and confirm both architectures still resolve afterwards.
Follow-up to #244 and #245. Two artefacts, opposite answers.
## Releases stay — all of them
They carry **no assets** (`v2026.9.2-pr244` has zero; the image lives in GHCR), so a release is a tag, a title and generated notes. And there is **no `CHANGELOG` file in this repository**, which makes those generated notes the only changelog the project has.
Deleting one destroys history that cannot be reconstructed and saves nothing. Rollback would survive — deleting a release does not delete its tag — but the record of what was in it would not.
## Images get pruned
A multi-arch build a week, and the by-digest push in `publish.yml` leaves **two untagged per-architecture manifests** behind each time on top of the tagged index. Ten tagged versions kept — roughly a quarter of a year, and far more than anything anyone rolls back to.
## The trap
The obvious tool is `actions/delete-package-versions` with `delete-only-untagged-versions: true`. It will delete the **per-architecture manifests that a multi-arch tag points at**, because they are untagged by design.
Nothing appears to break. The tag still resolves. Pulls just start failing for one architecture, at some later date, for a reason nobody connects to a cleanup job that ran weeks earlier.
`dataaxiom/ghcr-cleanup-action` understands manifest lists and will not orphan a retained index. On top of that:
- `validate: true` re-checks every remaining multi-arch manifest against the registry after the sweep — the step that would *catch* the footgun rather than leave it to be discovered on `docker pull`
- `exclude-tags: latest` takes `latest` out of consideration under any counting
- `delete-partial-images` / `delete-orphaned-images` sweep the wreckage of a half-failed run
## Pinned to a commit
`@d52806a0` (v1.2.2), not `@v1`. This action is handed `packages: write` and its entire purpose is deletion; a tag repointed upstream — by compromise or mistake — is not a risk worth carrying for the convenience of a floating major.
## Its own file, on purpose
`cleanup.yml` is `workflow_call` **and** `workflow_dispatch`, with `dry_run` defaulting to **true** on manual runs. That means the deletion list can be inspected without rebuilding and re-pushing an image to trigger it.
## Testing
`actionlint` clean across all three workflows. After merge I'll dispatch it with `dry_run: true` and read exactly what it proposes to delete before anything runs for real — and confirm both architectures still resolve afterwards.
**Merged** 2026-09-02 as coffey-labs/ihasmail@3ffee1224fe7
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
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.
Follow-up to #244 and #245. Two artefacts, opposite answers.
Releases stay — all of them
They carry no assets (
v2026.9.2-pr244has zero; the image lives in GHCR), so a release is a tag, a title and generated notes. And there is noCHANGELOGfile in this repository, which makes those generated notes the only changelog the project has.Deleting one destroys history that cannot be reconstructed and saves nothing. Rollback would survive — deleting a release does not delete its tag — but the record of what was in it would not.
Images get pruned
A multi-arch build a week, and the by-digest push in
publish.ymlleaves two untagged per-architecture manifests behind each time on top of the tagged index. Ten tagged versions kept — roughly a quarter of a year, and far more than anything anyone rolls back to.The trap
The obvious tool is
actions/delete-package-versionswithdelete-only-untagged-versions: true. It will delete the per-architecture manifests that a multi-arch tag points at, because they are untagged by design.Nothing appears to break. The tag still resolves. Pulls just start failing for one architecture, at some later date, for a reason nobody connects to a cleanup job that ran weeks earlier.
dataaxiom/ghcr-cleanup-actionunderstands manifest lists and will not orphan a retained index. On top of that:validate: truere-checks every remaining multi-arch manifest against the registry after the sweep — the step that would catch the footgun rather than leave it to be discovered ondocker pullexclude-tags: latesttakeslatestout of consideration under any countingdelete-partial-images/delete-orphaned-imagessweep the wreckage of a half-failed runPinned to a commit
@d52806a0(v1.2.2), not@v1. This action is handedpackages: writeand its entire purpose is deletion; a tag repointed upstream — by compromise or mistake — is not a risk worth carrying for the convenience of a floating major.Its own file, on purpose
cleanup.ymlisworkflow_callandworkflow_dispatch, withdry_rundefaulting to true on manual runs. That means the deletion list can be inspected without rebuilding and re-pushing an image to trigger it.Testing
actionlintclean across all three workflows. After merge I'll dispatch it withdry_run: trueand read exactly what it proposes to delete before anything runs for real — and confirm both architectures still resolve afterwards.Merged 2026-09-02 as coffey-labs/ihasmail@3ffee1224f
Rebuilt from: git history, session transcript.