build-push: drop the empty ${{ }} that silenced the registry login #155

Merged
admin_emi merged 1 commit from fix/build-push-empty-expression into dev 2026-08-01 18:18:46 +00:00
Owner

Forgejo evaluates ${{ }} expressions inside a step's run: script, comments included. An empty expression is a parse error, and the runner drops the step — no log line, no error, no failed status. The login step never ran, so every docker push went out anonymous.

The registry then answers unauthorized: reqPackageAccess, which reads exactly like a revoked token or a missing scope. It is neither.

Isolated on one branch, one variable, back to back:

run empty ${{ }} result
1520 present both legs fail, no Login Succeeded in log
1521 removed both legs pass, Login Succeeded at line 487, sha-df409f88b3fd published

Ruled out first, so nobody repeats it: the token, its scope, and repo-vs-organization placement. Pushes to jinemi/thermograph/* succeed by hand from vps1 and from the runner host, with matching and mismatched usernames, and the run log shows REGISTRY_TOKEN:*** arriving in the job environment — org-level secrets resolve fine.

Separately fixed out of band (same root cause, PR #151 moving ROOT_URL to dev.jinemi.com): the registry's bearer realm became https://dev.jinemi.com/v2/token, a name that only routes publicly, where caddy-git.conf deliberately 403s /v2/*. Mesh pins added to /etc/hosts on vps1, vps2 and the runner host, and --add-host=dev.jinemi.com:10.10.0.2 alongside the existing git.thermograph.org entry in the runner's config.yaml. Those pins are manual host state — nothing in the repo writes them.

Forgejo evaluates `${{ }}` expressions inside a step's `run:` script, comments included. An **empty** expression is a parse error, and the runner drops the step — no log line, no error, no failed status. The login step never ran, so every `docker push` went out anonymous. The registry then answers `unauthorized: reqPackageAccess`, which reads exactly like a revoked token or a missing scope. It is neither. Isolated on one branch, one variable, back to back: | run | empty `${{ }}` | result | |---|---|---| | 1520 | present | both legs fail, no `Login Succeeded` in log | | 1521 | removed | both legs pass, `Login Succeeded` at line 487, `sha-df409f88b3fd` published | Ruled out first, so nobody repeats it: the token, its scope, and repo-vs-organization placement. Pushes to `jinemi/thermograph/*` succeed by hand from vps1 and from the runner host, with matching and mismatched usernames, and the run log shows `REGISTRY_TOKEN:***` arriving in the job environment — org-level secrets resolve fine. Separately fixed out of band (same root cause, PR #151 moving ROOT_URL to dev.jinemi.com): the registry's bearer realm became `https://dev.jinemi.com/v2/token`, a name that only routes publicly, where `caddy-git.conf` deliberately 403s `/v2/*`. Mesh pins added to `/etc/hosts` on vps1, vps2 and the runner host, and `--add-host=dev.jinemi.com:10.10.0.2` alongside the existing `git.thermograph.org` entry in the runner's `config.yaml`. Those pins are manual host state — nothing in the repo writes them.
emig added 1 commit 2026-08-01 18:18:14 +00:00
build-push: drop the empty ${{ }} that silenced the registry login
All checks were successful
secrets-guard / encrypted (pull_request) Successful in 5s
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / changes (pull_request) Successful in 16s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
5f7075e2cf
Forgejo evaluates ${{ }} expressions inside a step's `run:` script, comments
included. An EMPTY expression is a parse error, and the runner's response is to
drop the step -- no log line, no error, no failed status. The login step simply
never ran, so every subsequent `docker push` went out anonymous.

The registry then answers `unauthorized: reqPackageAccess` (or, depending on
the client path, `no basic auth credentials`), which reads exactly like a
revoked token or a missing scope. It is neither. Both are downstream of a
comment.

Isolated on one branch, one variable, back to back:

  * empty expression present -> both legs fail, no `Login Succeeded` in the log
  * empty expression removed -> both legs pass, `Login Succeeded` present,
    sha-df409f88b3fd published for backend and frontend

Nothing was wrong with the credential. The token, its scope and whether it sat
at repo or organization level were all ruled out first: pushes to
jinemi/thermograph/* succeed by hand from vps1 and from the runner host, with
matching and mismatched usernames, and the run log shows REGISTRY_TOKEN
arriving in the job environment.

Do not write a bare ${{ }} in a run block, in a comment or otherwise.
admin_emi merged commit 62ba381d06 into dev 2026-08-01 18:18:46 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Jinemi/thermograph#155
No description provided.