A Go repository was transferred from
The solution is written to ~/SOLUTION.md. Here it is:
A Go repository was transferred from <old-owner> to <new-owner>. CI publishes with a dynamic expression:
images: ghcr.io/${{ github.repository }}
github.repository resolves to <new-owner>/<repo> after the transfer, so CI automatically started publishing ghcr.io/<new-owner>/<repo>. But README, docker-compose.prod.yml, the cross-platform quickstart, and the usage skill still hard-coded ghcr.io/<old-owner>/<repo>.
Both paths return HTTP 401 to anonymous docker manifest inspect, which hides the real defect: the old path is not published, while the new path is private and requires auth. The drift is a reference bug, not a package-visibility change.
| Layer | Before migration | After migration |
|---|---|---|
| Git remote | <old-owner>/<repo> |
<new-owner>/<repo> |
Workflow images: |
ghcr.io/${{ github.repository }} |
same (unchanged) |
| Resolved publish path | ghcr.io/<old-owner>/<repo> |
ghcr.io/<new-owner>/<repo> |
| README / Compose / quickstart / skill | old path (hard-coded) | still old → stale |
The workflow's namespace is derived from context, so the publish target moved with the transfer. Every literal consumer did not. The old path is unpublished; the new path is private and returns 401 anonymously. No package visibility policy changed.
git remote get-url origin # -> <new-owner>/<repo>
grep -RIn "ghcr.io/\${{ github.repository }}" .github/workflows/
gh api "orgs/<new-owner>/packages/container/<repo>" --jq '.name' 2>/dev/null || true
# every stale reference
grep -RIn --exclude-dir=.git "ghcr.io/<old-owner>/<repo>" .
Canonical path: ghcr.io/<new-owner>/<repo>. Note GHCR requires lowercase names — verify the workflow expression yields lowercase.
4.1 Optionally normalize casing in the workflow so the image resolves to a single lowercase value:
env:
IMAGE: ghcr.io/${{ github.repository }}
# normalize: echo "IMAGE=${IMAGE,,}" >> "$GITHUB_ENV"
4.2 Replace active references, then hand-check the four surfaces (README, docker-compose.prod.yml, quickstart docs, current usage skill):
grep -RIl --exclude-dir=.git "ghcr.io/<old-owner>/<repo>" . | while read -r f; do
sed -i 's#ghcr\.io/<old-owner>/<repo>#ghcr.io/<new-owner>/<repo>#g' "$f"
done
git diff --stat
- image: ghcr.io/<old-owner>/<repo>:latest
+ image: ghcr.io/<new-owner>/<repo>:latest
4.3 Preserve history explicitly — do not rewrite changelogs/release notes. Label them:
> **Historical (pre-migration):** prior to the transfer, images were published
> as `ghcr.io/<old-owner>/<repo>`. No longer the published path.
Rule: changelogs/dated records keep the old string with a historical marker; active runbooks/README/Compose/quickstart/skill use only the new path.
4.4 Keep source builds as the zero-auth recommendation:
git clone https://github.com/<new-owner>/<repo>.git && cd <repo>
go build ./... && go run . --help
4.5 Document authenticated pulls without claiming visibility changed:
echo "$CR_PAT" | docker login ghcr.io -u <github-username> --password-stdin
docker pull ghcr.io/<new-owner>/<repo>:latest
The image is a private GHCR package. Pulling needs a token with `read:packages`.
The owner move changed the path; it did not change visibility.
# 5.1 no stale path in active files (historical mentions excluded)
grep -RIn --exclude-dir=.git "ghcr.io/<old-owner>/<repo>" README.md docker-compose.prod.yml docs/ skills/ \
| grep -v -i historical && echo "STALE" || echo clean
# 5.2 compose resolves new image
docker compose -f docker-compose.prod.yml config | grep -i "image:"
# 5.3 build/vet/test
go build ./... && go vet ./... && go test -count=1 -short ./...
# 5.4 authenticated path check
docker manifest inspect ghcr.io/<new-owner>/<repo>:latest >/dev/null && echo "new OK"
Acceptance checklist: remote derives new owner; workflow resolves new path; all four active surfaces updated; old path only in labeled history; source build still the zero-auth path; docker login documented with read:packages and no visibility claim; docker compose config shows new image; build/vet/test pass; Actions green.
Key insight: the anonymous 401 on both paths is a red herring — the dynamic ${{ github.repository }} moved the publish target while hard-coded literals stayed behind. Fix the literals, not the package visibility.
# Evidence - Problem class: container-registry-image-path-drift-after-repository-owner-change - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-24T06:47:17.299Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A repository moved from one GitHub owner to another. The CI workflow used images: ghcr.io/${{ github.repository }}, so it automatically began publishing ghcr.io/<new-owner>/<repo>, while README, docker-compose.prod.yml, cross-platform quickstart, and usage guidance still named ghcr.io/<old-owner>/<repo>. Both anonymous manifest probes returned HTTP 401, masking the path drift as one generic package-visibility problem. Resolve the actual published path from git remote plus the workflow expression, update every active user-facing/runtime reference, preserve historical evidence as explicitly historical, keep source builds as the zero-auth recommendation, and document docker login ghcr.io with a read:packages token without claiming package visibility changed. Verify no old path remains in active files, docker compose config resolves the new image, build/vet/tests pass, and CI is green. Acceptance checks may include current usage skills as well as README and Compose; update them explicitly rather than treating every old path mention as immutable history.", "environment": "GitHub Actions docker/metadata-action publishing to GHCR for a Go repository whose GitHub owner changed; README, Compose, quickstart docs, and usage skill retain an old hard-coded owner", "language": "yaml-markdown", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "container-registry-image-path-drift-after-repository-owner-change", "provider": "openrouter", "solved_at": "2026-09-24T06:47:17.299Z", "version": "consensus master 589cec3"}The solution is written to ~/SOLUTION.md. Here it is:
A Go repository was transferred from <old-owner> to <new-owner>. CI publishes with a dynamic expression:
images: ghcr.io/${{ github.repository }}
github.repository resolves to <new-owner>/<repo> after the transfer, so CI automatically started publishing ghcr.io/<new-owner>/<repo>. But README, docker-compose.prod.yml, the cross-platform quickstart, and the usage skill still hard-coded ghcr.io/<old-owner>/<repo>.
Both paths return HTTP 401 to anonymous docker manifest inspect, which hides the real defect: the old path is not published, while the new path is private and requires auth. The drift is a reference bug, not a package-visibility change.
| Layer | Before migration | After migration |
|---|---|---|
| Git remote | <old-owner>/<repo> |
<new-owner>/<repo> |
Workflow images: |
ghcr.io/${{ github.repository }} |
same (unchanged) |
| Resolved publish path | ghcr.io/<old-owner>/<repo> |
ghcr.io/<new-owner>/<repo> |
| README / Compose / quickstart / skill | old path (hard-coded) | still old → stale |
The workflow's namespace is derived from context, so the publish target moved with the transfer. Every literal consumer did not. The old path is unpublished; the new path is private and returns 401 anonymously. No package visibility policy changed.
git remote get-url origin # -> <new-owner>/<repo>
grep -RIn "ghcr.io/\${{ github.repository }}" .github/workflows/
gh api "orgs/<new-owner>/packages/container/<repo>" --jq '.name' 2>/dev/null || true
# every stale reference
grep -RIn --exclude-dir=.git "ghcr.io/<old-owner>/<repo>" .
Canonical path: ghcr.io/<new-owner>/<repo>. Note GHCR requires lowercase names — verify the workflow expression yields lowercase.
4.1 Optionally normalize casing in the workflow so the image resolves to a single lowercase value:
env:
IMAGE: ghcr.io/${{ github.repository }}
# normalize: echo "IMAGE=${IMAGE,,}" >> "$GITHUB_ENV"
4.2 Replace active references, then hand-check the four surfaces (README, docker-compose.prod.yml, quickstart docs, current usage skill):
grep -RIl --exclude-dir=.git "ghcr.io/<old-owner>/<repo>" . | while read -r f; do
sed -i 's#ghcr\.io/<old-owner>/<repo>#ghcr.io/<new-owner>/<repo>#g' "$f"
done
git diff --stat
- image: ghcr.io/<old-owner>/<repo>:latest
+ image: ghcr.io/<new-owner>/<repo>:latest
4.3 Preserve history explicitly — do not rewrite changelogs/release notes. Label them:
> **Historical (pre-migration):** prior to the transfer, images were published
> as `ghcr.io/<old-owner>/<repo>`. No longer the published path.
Rule: changelogs/dated records keep the old string with a historical marker; active runbooks/README/Compose/quickstart/skill use only the new path.
4.4 Keep source builds as the zero-auth recommendation:
git clone https://github.com/<new-owner>/<repo>.git && cd <repo>
go build ./... && go run . --help
4.5 Document authenticated pulls without claiming visibility changed:
echo "$CR_PAT" | docker login ghcr.io -u <github-username> --password-stdin
docker pull ghcr.io/<new-owner>/<repo>:latest
The image is a private GHCR package. Pulling needs a token with `read:packages`.
The owner move changed the path; it did not change visibility.
# 5.1 no stale path in active files (historical mentions excluded)
grep -RIn --exclude-dir=.git "ghcr.io/<old-owner>/<repo>" README.md docker-compose.prod.yml docs/ skills/ \
| grep -v -i historical && echo "STALE" || echo clean
# 5.2 compose resolves new image
docker compose -f docker-compose.prod.yml config | grep -i "image:"
# 5.3 build/vet/test
go build ./... && go vet ./... && go test -count=1 -short ./...
# 5.4 authenticated path check
docker manifest inspect ghcr.io/<new-owner>/<repo>:latest >/dev/null && echo "new OK"
Acceptance checklist: remote derives new owner; workflow resolves new path; all four active surfaces updated; old path only in labeled history; source build still the zero-auth path; docker login documented with read:packages and no visibility claim; docker compose config shows new image; build/vet/test pass; Actions green.
Key insight: the anonymous 401 on both paths is a red herring — the dynamic ${{ github.repository }} moved the publish target while hard-coded literals stayed behind. Fix the literals, not the package visibility.
# Evidence - Problem class: container-registry-image-path-drift-after-repository-owner-change - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-24T06:47:17.299Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A repository moved from one GitHub owner to another. The CI workflow used images: ghcr.io/${{ github.repository }}, so it automatically began publishing ghcr.io/<new-owner>/<repo>, while README, docker-compose.prod.yml, cross-platform quickstart, and usage guidance still named ghcr.io/<old-owner>/<repo>. Both anonymous manifest probes returned HTTP 401, masking the path drift as one generic package-visibility problem. Resolve the actual published path from git remote plus the workflow expression, update every active user-facing/runtime reference, preserve historical evidence as explicitly historical, keep source builds as the zero-auth recommendation, and document docker login ghcr.io with a read:packages token without claiming package visibility changed. Verify no old path remains in active files, docker compose config resolves the new image, build/vet/tests pass, and CI is green. Acceptance checks may include current usage skills as well as README and Compose; update them explicitly rather than treating every old path mention as immutable history.", "environment": "GitHub Actions docker/metadata-action publishing to GHCR for a Go repository whose GitHub owner changed; README, Compose, quickstart docs, and usage skill retain an old hard-coded owner", "language": "yaml-markdown", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "container-registry-image-path-drift-after-repository-owner-change", "provider": "openrouter", "solved_at": "2026-09-24T06:47:17.299Z", "version": "consensus master 589cec3"}