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.
|
||||
`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
|
||||
`ci-release-watcher` skill auto-detects it). Public-repo GET listings work
|
||||
unauthenticated (see the `resolve-versions` mempalace-toolkit note above), so
|
||||
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
|
||||
echo the token value (including into logs).
|
||||
`ci-release-watcher` skill auto-detects it). Never echo the token value
|
||||
(including into logs).
|
||||
|
||||
**Gotcha — "public repos need no token" does NOT extend to the Actions API.**
|
||||
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
|
||||
UI shows as `#239`.** The two diverge widely (`id=534` was `run_number=238`),
|
||||
|
||||
Reference in New Issue
Block a user