docs: "public repo" does not mean "readable CI" — the Actions API always needs a token
The Gitea-token section claimed "Public-repo GET listings work unauthenticated,
so the token matters mainly for private repos or rate-limit headroom", while the
same paragraph recommends the token for "inspecting CI runs". Those two
statements are incompatible, and the optimistic one is wrong.
Measured 2026-08-15 on gitea.jordbo.se against joakimp/pi-devbox (private=false):
GET /api/v1/repos/joakimp/pi-devbox -> 200
GET /api/v1/repos/joakimp/mempalace-toolkit/tags -> 200
GET /api/v1/repos/joakimp/mempalace-toolkit/commits -> 200
GET /api/v1/repos/joakimp/pi-devbox/actions/runs -> 401 "token is required"
So the unauthenticated claim holds for repo metadata, tags, and commit listings
(which is what the resolve-versions mempalace-toolkit note above relies on, and
that note stays correct), but NOT for any /actions/* endpoint. Narrowed the claim
rather than deleting it. Note the workflow comment in pi-devbox
.gitea/workflows/docker-publish.yml ("Gitea API requires auth even for
public-repo commit listing") is wrong in the opposite direction — harmless, since
it passes auth anyway, but it is not evidence for the strong claim.
This cost a real detour while triaging the v1.8.0 smoke failure: repo -> 200 was
read as "public, so CI is readable", then actions/runs 401'd.
Also recorded two things learned in the same session:
- The env GITEA_ACCESS_TOKEN was REVOKED, not stale. Proved it by comparing
sha256 fingerprints (not values) against the host's compose .env: identical
fingerprint, and 401 from the host too. Container env is baked at START, so a
host-side renewal is invisible until recreate. Added /api/v1/version as the
cheap "is this token alive at all" probe.
- With no usable token, Docker Hub tags localise a CI failure, because smoke
gates build-variant. A fresh base-<hash> with no vX.Y.Z and latest still on the
previous release == "base built, smoke failed, nothing shipped". That is how
v1.8.0 was diagnosed before a token existed; the logs later confirmed it
exactly, including the corollary that a failed (not skipped) smoke proves
GITEA_BUILD_TOKEN was alive in CI.
This commit is contained in:
@@ -148,11 +148,46 @@ Gitea API interaction from inside the container — inspecting CI runs,
|
|||||||
checking published tags, listing commits — e.g.
|
checking published tags, listing commits — e.g.
|
||||||
`curl -H "Authorization: token $GITEA_ACCESS_TOKEN" "$GITEA_HOST/api/v1/repos/joakimp/opencode-devbox/actions/runs?limit=5"`.
|
`curl -H "Authorization: token $GITEA_ACCESS_TOKEN" "$GITEA_HOST/api/v1/repos/joakimp/opencode-devbox/actions/runs?limit=5"`.
|
||||||
Prefer this over a short-lived PAT file when the env token is present (the
|
Prefer this over a short-lived PAT file when the env token is present (the
|
||||||
`ci-release-watcher` skill auto-detects it). Public-repo GET listings work
|
`ci-release-watcher` skill auto-detects it). Never echo the token value
|
||||||
unauthenticated (see the `resolve-versions` mempalace-toolkit note above), so
|
(including into logs).
|
||||||
the token matters mainly for private repos or rate-limit headroom; its
|
|
||||||
lifecycle is host-managed, so there is nothing to revoke after use. Never
|
**Gotcha — "public repos need no token" does NOT extend to the Actions API.**
|
||||||
echo the token value (including into logs).
|
Repo metadata, tags, and commit listings on a public repo are readable
|
||||||
|
unauthenticated (measured 2026-08-15 on `gitea.jordbo.se`: `GET /repos/{o}/{r}`,
|
||||||
|
`/tags`, `/commits?limit=1&sha=main` → all `200`; this is what the
|
||||||
|
`resolve-versions` mempalace-toolkit note above relies on). But **every
|
||||||
|
`/actions/*` endpoint returns `401 {"message":"token is required"}` even when
|
||||||
|
`private=false`** — including `/actions/runs`, `/actions/runs/<id>/jobs`, and
|
||||||
|
`/actions/jobs/<id>/logs`. So the token is *mandatory* for exactly the use case
|
||||||
|
this section opens with (inspecting CI runs), not merely "nice for private repos
|
||||||
|
or rate-limit headroom". Cost a wasted detour once: `repo → 200` was read as
|
||||||
|
"public, so CI is readable", which it is not.
|
||||||
|
|
||||||
|
**Gotcha — the env token can be silently REVOKED, and renewing the host `.env`
|
||||||
|
does not reach a running container.** The container's environment is baked at
|
||||||
|
**start**, so a host-side token renewal is invisible until the container is
|
||||||
|
recreated (or the value is re-read explicitly over SSH). Worse, a revoked token
|
||||||
|
looks identical to a valid one — the failure only shows up as `401` per request.
|
||||||
|
Verify before concluding anything about a repo's visibility:
|
||||||
|
`curl -s -o /dev/null -w '%{http_code}' -H "Authorization: token $GITEA_ACCESS_TOKEN" "$GITEA_HOST/api/v1/version"` → `200` means the token
|
||||||
|
itself is alive. To tell staleness from revocation, compare fingerprints rather
|
||||||
|
than values: `printf '%s' "$GITEA_ACCESS_TOKEN" | sha256sum` against
|
||||||
|
`shasum -a 256` of the token in the host's compose `.env` — identical
|
||||||
|
fingerprints that both `401` mean the token is revoked upstream, not stale in
|
||||||
|
the container.
|
||||||
|
|
||||||
|
**Workaround — with no usable token, Docker Hub tags localise a CI failure.**
|
||||||
|
The job graph makes registry state a progress oracle, because `smoke` *gates*
|
||||||
|
`build-variant` and `build-variant` gates `promote-base-latest`. Read
|
||||||
|
`https://hub.docker.com/v2/repositories/<ns>/<img>/tags?page_size=100` (public,
|
||||||
|
no auth) and compare timestamps: a fresh `base-<hash>` with **no** `vX.Y.Z` tag
|
||||||
|
and `latest` still on the previous release pinpoints "base built, smoke failed,
|
||||||
|
nothing shipped" without reading a single log line. Diagnosed v1.8.0 this way
|
||||||
|
before a token was available; the log later confirmed it exactly. A second free
|
||||||
|
deduction from the same graph: since `smoke`'s `if:` requires
|
||||||
|
`needs.resolve-versions.result == 'success'`, a *failed* smoke proves
|
||||||
|
`GITEA_BUILD_TOKEN` was alive in CI — a dead build token would have **skipped**
|
||||||
|
smoke (via `require_sha`), not failed it.
|
||||||
|
|
||||||
**Gotcha — the jobs endpoint takes the internal `id`, NOT the `run_number` the
|
**Gotcha — the jobs endpoint takes the internal `id`, NOT the `run_number` the
|
||||||
UI shows as `#239`.** The two diverge widely (`id=534` was `run_number=238`),
|
UI shows as `#239`.** The two diverge widely (`id=534` was `run_number=238`),
|
||||||
|
|||||||
Reference in New Issue
Block a user