:latest images ship InformationalVersion 0.0.0 — a --depth=2 fetch grafts the build job's full clone shallow
#836
Closed
opened 2026-08-26 18:22:19 +02:00 by timothy
·
2 comments
No Branch/Tag Specified
main
renovate/meziantou.analyzer-3.x
release/v26.15.0-notes
fix/830-add-items-error-surface
renovate/lucene.net
renovate/cliwrap-3.x
issue-806-guard-populations
renovate/dotnet-monorepo
scratch/767b-poisoned
scratch/767b-control
release/v26.14.0-notes
release/v26.14.0
renovate/sqlitepclraw.bundle_e_sqlite3-3.x
docs/510-skill-logo-bug-policy
fix/510-watermark-resolution-policy
fix/629-verdict-classifier-falseopens
fix/609-decisions-edit-token-scope
issue-135-clear-to-none
release/v26.12.0-notes
fix/409b-lastscan-api-parity
fix/401-updatechannel-mirror-422
fix/327-playlist-rename-validation
fix/410-scancancel-log-level
fix/409-447-librariesscreen-neverscanned
fix/338-zap-exit-code
fix/367-plex-budget-message
fix/310-debom-legacy-cs
ci/604-lane-rebalance
feat/388-design-mirror
feat/247-test-ownership
feat/247-primary-action
feat/357-player-owned-playback
feat/357-jellyfin-plugin-poc
fix/289-mcp-hardening
issue58-mcp
feat/244-channels-extract
ci/auto-bump-prod-compose
feat/multi-rerun-collections-api
feat/collections-api
feat/quick-wins
feat/185-docs-part2
feat/140-collections-screen
feat/146-channel-edit
feat/147-classic-ui-link
issue22-renovate-dashboard
feat/91-cutover
feat/63-composite-create
feat/65-library-browse
feat/85-epg
feat/86-schedule-editor
feat/109-dashboard-data
feat/99-session-tracking
fix/dockerfile-node-tag
feat/59-spa-foundation
docs/59-ui-redesign-brief
feat/102-json-guide
feat/111-schedule-durations
feat/104-artwork-upload
feat/103-media-sources-api
feat/playouts-read-api
feat/108-health-api
feat/105-picker-list-endpoints
issue-97-channel-state-api
issue42-jellyfin-musicvideos
issue46-rest-api-error-contract
dependabot/nuget/ErsatzTV.FFmpeg.Tests/multi-d307a2e06f
qsv-improvements
hdr-vulkan-cuda-test
v26.15.0
v26.14.0
v26.13.0
v26.12.0
v26.11.0
v26.10.0
v26.9.0
v26.8.0
v26.7.0
blazor-final
v26.6.0
v26.5.0
v26.4.0
v26.3.1
v26.3.0
v26.2.0
v26.1.1
v26.1.0
v25.9.0
v25.8.0
v25.7.1
v25.7.0
v25.6.0
v25.5.0
v25.4.0
v25.3.1
v25.3.0
v25.2.0
v25.1.0
v0.8.8-beta
v0.8.7-beta
v0.8.6-beta
v0.8.5-beta
v0.8.4-beta
v0.8.3-beta
v0.8.2-beta
v0.8.1-beta
v0.8.0-beta
v0.7.9-beta
v0.7.8-beta
v0.7.7-beta
v0.7.6-beta
v0.7.5-beta
v0.7.4-beta
v0.7.3-beta
v0.7.2-beta
v0.7.1-beta
v0.7.0-beta
v0.6.9-beta
v0.6.8-beta
v0.6.7-beta
v0.6.6-beta
v0.6.5-beta
v0.6.4-beta
v0.6.3-beta
v0.6.2-beta
v0.6.1-beta
v0.6.0-beta
v0.5.8-beta
v0.5.7-beta
v0.5.6-beta
v0.5.5-beta
v0.5.4-beta
v0.5.3-beta
v0.5.2-beta
v0.5.1-beta
v0.5.0-beta
v0.4.5-alpha
v0.4.4-alpha
v0.4.3-alpha
v0.4.2-alpha
v0.4.1-alpha
v0.4.0-alpha
v0.3.8-alpha
v0.3.7-alpha
develop
v0.3.6-alpha
v0.3.5-alpha
v0.3.4-alpha
v0.3.3-alpha
v0.3.2-alpha
v0.3.1-alpha
v0.3.0-alpha
v0.2.5-alpha
v0.2.4-alpha
v0.2.3-alpha
v0.2.2-alpha
v0.2.1-alpha
v0.2.0-alpha
v0.1.5-alpha
v0.1.4-alpha
v0.1.3-alpha
v0.1.2-alpha
v0.1.1-alpha
v0.1.0-alpha
v0.0.62-alpha
v0.0.61-alpha
v0.0.60-alpha
v0.0.59-alpha
v0.0.58-alpha
v0.0.57-alpha
v0.0.56-alpha
v0.0.55-alpha
v0.0.54-alpha
v0.0.53-alpha
v0.0.52-alpha
v0.0.51-alpha
v0.0.50-alpha
v0.0.49-prealpha
v0.0.48-prealpha
v0.0.47-prealpha
v0.0.46-prealpha
v0.0.45-prealpha
v0.0.44-prealpha
v0.0.43-prealpha
v0.0.42-prealpha
v0.0.41-prealpha
v0.0.40-prealpha
v0.0.39-prealpha
v0.0.38-prealpha
v0.0.37-prealpha
v0.0.36-prealpha
v0.0.35-prealpha
v0.0.34-prealpha
v0.0.33-prealpha
v0.0.32-prealpha
v0.0.31-prealpha
v0.0.30-prealpha
v0.0.29-prealpha
v0.0.28-prealpha
v0.0.27-prealpha
v0.0.26-prealpha
v0.0.25-prealpha
v0.0.24-prealpha
v0.0.23-prealpha
v0.0.22-prealpha
v0.0.21-prealpha
v0.0.20-prealpha
v0.0.19-prealpha
v0.0.18-prealpha
v0.0.17-prealpha
v0.0.16-prealpha
v0.0.15-prealpha
v0.0.14-prealpha
v0.0.13-prealpha
v0.0.12-prealpha
v0.0.11-prealpha
v0.0.10-prealpha
v0.0.9-prealpha
v0.0.8-prealpha
v0.0.7-prealpha
v0.0.6-prealpha
v0.0.5-prealpha
v0.0.4-prealpha
v0.0.3-prealpha
v0.0.2-prealpha
v0.0.1-prealpha
Labels
Clear labels
ad-hoc
api
bug
ci-cd
content
dependencies
enhancement
frontend
in-progress
jellyfin
parked
priority: high
priority: low
priority: medium
review
security
One-off / ad-hoc work not tracked by a dedicated issue
REST API / HTTP endpoints
Something isn't working
Build, test, deploy pipeline
Channel content / schedules / playlists
Dependency updates (Renovate)
New feature or improvement
ChicoryTV React SPA frontend
Claimed by an active session — do not pick up
Jellyfin tuner / IPTV integration
Excluded from automatic queue pickup; work only when explicitly selected
Adversarial review finding
Security / vulnerability fix
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: timothy/ersatztv#836
Reference in New Issue
Block a user
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.
Found while verifying #746's fetch changes; pre-existing, unrelated to that PR, and currently shipping.
Measured
The
ersatztv-testcontainer on jazz (tracking:latest) carries:No
26.x.y-<sha>string is present in the assembly at all. So every:latestimage built from a push tomainreportsInformationalVersion = 0.0.0-<sha>instead of26.3.1-<sha>.Re-confirmed from CI 2026-08-29, run 2416 / job 10345 (push to
mainat8aeacd534):INFO_VERSION=0.0.0-8aeacd53.Mechanism
docker-build.yml'sbuildjob runs, in this order:Checkoutwithfetch-depth: 0— full history, tags included.Detect docs-only changes→scripts/ci-detect-docs-only.sh, whose push path runsgit fetch --no-tags --depth=2 origin "${GITHUB_REF_NAME:-main}"(scripts/ci-detect-docs-only.sh:70).Compute version and tags→DESC=$(git describe --tags --abbrev=0 2>/dev/null || echo v0.0.0).git fetch --depth=Ngrafts a complete clone shallow —.git/shallowis created and history is cut at N even though the objects are present. The taggit describeneeds is beyond that boundary, so step 3'sgit describefails,2>/dev/null || echo v0.0.0swallows it, and the build proceeds withv0.0.0.The graft behaviour is the same one measured in #746 (150-commit repo, branch point 130 back,
--depth=100→.git/shallowcreated →git diff origin/main...HEAD→fatal: no merge base; the same fetch without--depthresolves).Why it has stayed invisible
GITHUB_REF_NAME. So:prod/:<version>images are correctly versioned and only:latestis wrong.|| echo v0.0.0is a fallback that cannot fail, so nothing ever goes red.Fix sketch (decide, do not assume)
The
--depth=2exists soHEAD^1resolves in thetest/migrationsjobs' shallowfetch-depth: 2checkouts, where it is correct and necessary. Inbuild'sfetch-depth: 0checkout it is both unnecessary and harmful. Options: skip the fetch when the clone is already complete (git rev-parse --is-shallow-repository), or unshallow after the detect step, or split the depth choice by checkout depth. Whichever is chosen, assert the resulting version rather than the exit status.Done-when
Boxes 1 and 2 as originally written were unsatisfiable before the merge they gate: the
buildjob only runs on a push tomain, and:latestonly exists after that push, so "demonstrated from a run log" and "read it back out of the image" could never be ticked while the PR was open. They are restated below as what is verifiable pre-merge — deliberately not weaker, since a reproduction against the real repository discriminates the mechanism better than a post-merge run log does — and the literal image read is moved to a post-merge confirmation recorded in the closing record.git describefails (-> 0.0.0-<sha>); the fixed detector leaves it complete andgit describeresolves (-> 26.14.0-<sha>)buildjob'sCompute version and tagsstep body executed against a grafted clone (exit 1, no image), a complete clone (26.14.0-<sha>), and the tag path (:prod+26.14.0, unaffected even on a grafted clone)|| echo v0.0.0fallback either goes away or is accompanied by something that notices when it firesdocs/guard-inventory.mdrowdocs/ci-cd.mdrecords the depth-graft hazard where the detector scripts are describedPost-merge confirmation (not a merge gate): read
InformationalVersionback out of the newly built:latestimage and record it in the## Closing record.Claiming this (Claude Code / Opus 5, orchestrator session, 2026-08-29).
Pre-claim checks per
process.parallel-session-claim, all clear:#878,#879,#882are #830/#823/#824/#812)git ls-remote --heads origin '*836*': no branchgit fetch origin main: at8aeacd534Bundle scan (all three axes): no milestone; no cross-reference comment; shared label
ci-cdhas #853, #855, #869 open. Not folding them in — #855 is a whole new coupling guard (parser + mutation + inventory row) and #869 is a claim re-probe sweep; neither shares a mechanism or a file with the depth-graft fix, so bundling would only widen the review surface.Closing record
Outcome: Fixed and merged — PR #884, squashed to
94a3d1349.scripts/ci-detect-docs-only.shroutes both fetch sites throughfetch_ref, which passes--depthonly whengit rev-parse --is-shallow-repositoryanswerstrue;docker-build.yml'sCompute version and tagsnow fails the job on agit describefailure instead of stamping0.0.0. Shipsscripts/tests/test_docs_only_detector_clone_depth.py(6 cases), a declared clause mutation inmutation_manifest.py, and the decision recordci.fetch-depth-never-grafts-a-complete-clone.Root cause:
git fetch --depth=NGRAFTS a complete clone shallow — it writes.git/shallowand cuts history at N even though every object is already present. The script has exactly four consumers and they disagree on checkout depth (test/migrations/functional-e2eatfetch-depth: 2,buildatfetch-depth: 0); it applied a depth chosen for the first group to all of them. The taggit describeneeded was beyond the boundary, and|| echo v0.0.0— a fallback that cannot fail — converted the failure into a version. Live from 2026-07-17 (#416, which introduced the depth) until this change.Decisions/conventions changed: Added
ci.fetch-depth-never-grafts-a-complete-clone. Promotedscripts/ci-detect-docs-only.shindocs/guard-inventory.mdfromGUARD | NONEtoGUARD | MUTATION, and removed it from the prose "needs its own harness" backlog. Its recorded reason for being undeclared — "it feeds the skip gate, so its effect is visible only in a workflow run" — was measured false: the graft is a flag on a real clone and is observable in-process.Reusable knowledge:
grep -rn <script> .gitea/, never by looking for a step withid: detect—api-docsandformathave one of those and run their own inline diff. An earlier draft of this change read them as consumers and said so in the decision record; cold review caught it.file://remote is mandatory when testing shallow behaviour.git clone /pathuses the local transport, which ignores--depthoutright, so a fixture built on a plain path never grafts and every assertion holds vacuously. The guard carries a negative control that requires the fixture to graft.--depth=200has nothing to cut; it passed against the unfixed script. The control is now parametrised over both (fixture, depth) pairs.-c user.email/-c user.nameon every call plususer.useConfigOnly=true; pointingGIT_CONFIG_GLOBALatos.devnullis not enough, because macOS git then invents an identity from the OS user and a dropped flag still passes locally.Verification:
main, post-merge. Run 2476 / job 10631 log:checkout is not shallow (is-shallow=false); fetching without --depth (ersatztv#836)thenINFO_VERSION=26.14.0-94a3d134, anddocker buildx build --build-arg INFO_VERSION=26.14.0-94a3d134. The immediately precedingmainbuild (run 2472,e8f80c42c) showsINFO_VERSION=0.0.0-e8f80c42— a clean A/B across the merge.describefails (-> 0.0.0-1afad085); the fixed one leaves it complete (-> 26.14.0-1afad085). TheCompute version and tagsstep body executed against a grafted clone (exit 1, no image), a complete clone, and the tag path (:prod+26.14.0, unaffected even when grafted).d44d9eb55: 14 success + 1 correctly-skipped, both required contexts andreview-verdict/h10. Local: ruff clean over 52 tracked files with the CI-pinned 0.12.11;scripts/tests1250 passed, 2 skipped.Deferred: The promised post-merge step "read
InformationalVersionback out of the newly built:latestimage" could not be performed, and this is stated rather than quietly dropped: no new image was published, becauseBuild & push image (amd64)fails at the Dockerfile's web stage for an unrelated, pre-existing reason — the same failure is on the precedingmaincommit. Filed as #887 (priority: high, since it also breaks the tag/release path). The version evidence above comes from the run log and the--build-argactually passed todocker buildx, which is the same value that would have been read back out of the image. The registry's newest:lateststill reads0.0.0-761e5758and will until #887 is fixed.Docs updated:
docs/ci-cd.md(its prose described the defect in the present tense and is now the end state),docs/guard-inventory.md(row regrade + new PROOF row + summary counts),docs/decisions/records/ci/fetch-depth-never-grafts-a-complete-clone.md(new),docs/decisions/README.md(regenerated catalog).