Skip to content

fix: follow GHCR pagination when fetching tags, not just the first page#31

Open
stigdreyer wants to merge 1 commit into
halos-org:mainfrom
stigdreyer:fix/ghcr-tag-pagination
Open

fix: follow GHCR pagination when fetching tags, not just the first page#31
stigdreyer wants to merge 1 commit into
halos-org:mainfrom
stigdreyer:fix/ghcr-tag-pagination

Conversation

@stigdreyer

Copy link
Copy Markdown

Summary

fetch_tags()'s GHCR branch requests n=10000 but never follows the registry's Link: rel="next" pagination header — it just takes whatever the first response contains. GHCR silently caps the actual page size below the requested n for tag-heavy images, so any image with more tags than that cap loses everything past the first page, with no error or warning.

How this was found

check-image-updates.sh produced a PR against a downstream repo proposing to change ghcr.io/home-assistant/home-assistant from 2026.7.0 (the live-running version) to 2023.3.6 — a 3+ year downgrade masquerading as an "update".

Confirmed directly against the registry:

$ curl -s -D - -o /dev/null -H "Authorization: Bearer $TOKEN" \
    "https://ghcr.io/v2/home-assistant/home-assistant/tags/list?n=10000" | grep -i link
link: </v2/home-assistant/home-assistant/tags/list?last=2023.4.0b2&n=10000>; rel="next"

Despite requesting n=10000, GHCR returned exactly 1000 tags and a Link header pointing at the rest. Tags come back in lexicographic order, so this isn't a random subset — it's a full page of the earliest-sorting tags (up to 2023.4.0b2) with a hard stop, and 2026.x releases exist entirely past that cutoff, unseen.

Fix

  • GHCR: follow Link: rel="next" in a loop until absent, accumulating tags across all pages.
  • Docker Hub: same class of bug existed (the response's next field was ignored) — hardened the same way, though I haven't observed it bite in practice since that endpoint orders by last_updated rather than lexicographically. Same silent-truncation risk for any image with enough tags, though.

Tested

Extracted fetch_tags() and ran it directly against the real registries:

  • ghcr.io/home-assistant/home-assistant: now returns 4327 tags (vs. 1000 before), correctly including 2026.7.0, 2026.7.1, 2026.7.2
  • Ran the full script end-to-end against a throwaway compose fixture with homeassistant:2026.7.0 as the "current" tag: now correctly computes Latest: 2026.7.2 (previously would have computed 2023.3.6, per the actual downstream PR this surfaced from)
  • ghcr.io/music-assistant/server (small image, genuinely single page, no Link header): unaffected, no regression — loop terminates immediately as before

Did not modify tag_pattern() or the sort -V comparison logic — those were already correct; the only issue was the tag list being incomplete before it ever reached them.

GHCR silently caps tags/list responses below whatever `n` is requested
(observed: n=10000 request, 1000 tags actually returned, with a
Link: rel="next" header pointing at the rest) — and fetch_tags() never
followed it, so any image with more tags than that cap silently lost
everything past the first page.

Tags come back in lexicographic order, so the truncation isn't random:
it's a full page of the earliest-sorting tags, then a hard stop. For
ghcr.io/home-assistant/home-assistant (years of near-daily CalVer
releases, thousands of tags), this meant "latest" resolved to 2023.3.6
while 2026.7.2 existed entirely unseen past the cutoff — an update-PR
downgrading a live 2026.7.0 install by three years.

Also hardens the Docker Hub path the same way (its JSON response's
`next` field was likewise ignored) — same class of bug, not yet
observed to bite in practice since that endpoint orders by
last_updated rather than lexicographically, but the same silent-
truncation risk applies to any image with enough tags.

Verified against the real registries: home-assistant/home-assistant
now correctly returns 2026.7.2 as latest (4327 tags fetched across
pages, vs. 1000 before), and a single-page image (music-assistant/server)
is unaffected.
stigdreyer added a commit to stigdreyer/ziganka-containers that referenced this pull request Jul 17, 2026
The auto-update workflow's version check hit a GHCR pagination bug
(fixed upstream: halos-org/shared-workflows#31) and proposed
downgrading from the live-running 2026.7.0 to 2023.3.6 — a 3+ year
regression, not an update. Corrected to the real latest release
(home-assistant/core's actual latest GitHub release, confirmed present
as a ghcr.io tag).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
stigdreyer pushed a commit to stigdreyer/ziganka-containers that referenced this pull request Jul 17, 2026
Corrected the Home Assistant target to the actual latest release (2026.7.2) before merging — the automated PR had proposed a downgrade to 2023.3.6 due to a GHCR pagination bug in the update checker (fixed upstream: halos-org/shared-workflows#31).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant