fix(web): name the scope problem behind a Gitea 403

Both listing endpoints answering 403 means Gitea authenticated the token and
then rejected its scope -- exactly what an OIDC login token with only
`openid profile` gets. The card showed a bare "Gitea API returned HTTP 403",
which sends the operator hunting the wrong thing (admin rights, allowlist,
URL). Say what it is and name both fixes: add an API read scope to
`[web.oidc] scopes`, or configure the keyring PAT fallback. A 403 on the admin
listing alone still falls through to the user listing as before, and a 404
there keeps its plain message.
This commit is contained in:
2026-07-25 15:03:25 +02:00
parent af7dee60e5
commit 4aedd937d7
2 changed files with 34 additions and 1 deletions
@@ -54,6 +54,15 @@ _RUNNING = "running"
_QUEUED = "queued"
_UNKNOWN = "unknown"
# Shown when Gitea accepts the token but refuses both listing endpoints: the
# token carries no API read scope. Names both fixes, since which one applies
# depends on whether the operator can add scopes to the OAuth2 app.
_SCOPE_HINT = (
"Gitea accepted the token but refused it (HTTP 403): it carries no API "
"read scope. Add read:repository (or read:admin) to [web.oidc] scopes and "
"log in again, or configure a keyring PAT via [web] gitea_token_cred."
)
# Gitea's ``status`` field, for runs that have not reached a conclusion yet.
_STATUS_MAP = {
"in_progress": _RUNNING,
@@ -250,6 +259,11 @@ def _fetch_runs(
if exc.code in (403, 404) and source == "admin":
# Token is not admin-scoped: try the per-user listing.
continue
if exc.code == 403:
# Both endpoints refused an otherwise-valid token: Gitea
# authenticated it and then rejected its scope. Say so --
# "HTTP 403" alone sends the operator hunting the wrong thing.
last_error = _SCOPE_HINT
break
except (urllib.error.URLError, TimeoutError, OSError) as exc:
last_error = f"Gitea unreachable: {exc}."