Files
home-network-k3s-impl/decisions/2026-10-30-renovate-auth.md
jakman85andClaude Opus 5.5 0c6ee4921d
gate / gate (pull_request) Successful in 2m54s
gate / conformance (pull_request) Has been skipped
fix(renovate): document and probe the five token scopes Renovate needs; lint daemon-bound actions
Run 350 cleared platform init (/user 200, /version 200, /repos 200), then stopped at
GET /api/v1/orgs/Home-Network with Gitea's own 403:
  token does not have at least one of required scope(s), required=[read:organization],
  token scope=read:package,write:repository,read:user
The workflow's header listed only those three scopes, and the operator minted exactly that.
Renovate's initRepo -> isOrg needs read:organization, and Gitea files /issues and /labels under
the issue scope, which the dashboard and PR labels need: write:issue.

- renovate.yaml: corrects the scope list (read:user, read:organization, write:repository,
  write:issue, read:package), with the call each serves. A new preflight step probes them with
  GETs, the token passed to curl on stdin, and names a missing scope instead of Renovate's bare
  "Authentication failure".
- ci/lint.sh: rejects workflow `uses:` lines that need a Docker daemon (docker://,
  renovatebot/github-action, docker/*-action), the class fixed in #84.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 01:55:39 -05:00

6.0 KiB
Raw Permalink Blame History

date, branch, title
date branch title
2026-10-30 fix/renovate-auth Renovate's token needs five scopes, not three; the workflow documented the wrong list, and now probes it

What changed

The fix is operator-side. RENOVATE_TOKEN lacks read:organization, and it will next lack write:issue. The operator must re-mint it with five scopes:

  • read:user
  • read:organization
  • write:repository
  • write:issue
  • read:package

The git half fixes the two things that sent the operator round this loop twice.

.gitea/workflows/renovate.yaml:

  • The header's scope list is corrected, with the call each scope serves. The old list named only write:repository, read:user and read:package, and the operator minted exactly that. The preflight's "TO FIX" text is corrected the same way.
  • A new step, "Preflight - the token has every scope Renovate needs".
    • It probes read-only GETs: /user for read:user, /orgs/Home-Network for read:organization, /repos/{repo} for read:repository, /repos/{repo}/labels for read:issue, and registry /v2/token for read:package.
    • It reads the token's own scope list out of Gitea's 403 from /admin/orgs (names only), and requires write:repository and write:issue in it.
    • It says what a failure means: 401 is the value; a Gitea 403 is a scope, and the probe prints Gitea's message; an "Access denied" 403 is a Cilium/Envoy denial.
    • The token is fed to curl on stdin (--config -), never in argv.

ci/lint.sh: a new guard rejects any non-comment uses: in .gitea/workflows that needs a Docker daemon: docker://, renovatebot/github-action and Docker's daemon-bound docker/*-actions. That is the class of bug fixed in #84.

Root cause, with evidence

Run 350 (task 567, 06:49 UTC) got further than 348 and 349. Gitea's router log, filtered to the act-runner pod 10.0.0.38:

06:49:31 GET /api/v1/user                                   200 OK
06:49:31 GET /api/v1/version                                200 OK
06:49:31 GET /api/v1/repos/Home-Network/home-network-k3s-impl  200 OK
06:49:31 GET /api/v1/orgs/Home-Network                      403 Forbidden

There were no /v2/ requests: Renovate stopped before extraction. From the job log, verbatim:

ERROR: Repository has unknown error (repository=Home-Network/home-network-k3s-impl)
  "url": "https://gitea.home.keithjackson.dev/api/v1/orgs/Home-Network",
  "message": "token does not have at least one of required scope(s), required=[read:organization], token scope=read:package,write:repository,read:user"
  "x-envoy-upstream-service-time": "27", "server": "envoy"
INFO: Repository finished ... "result": "unknown-error", "exitCode": 8

This is not Cilium. The body is Gitea's JSON scope message, Gitea logged the request, and x-envoy-upstream-service-time shows that Envoy forwarded it upstream.

The secret WAS updated. Runs 348 and 349 got GET /user 403 from tokenRequiresScopes. Run 350 got /user 200, and its token scope list now includes read:user.

Why read:organization is needed. Renovate 44.167.0, lib/modules/platform/gitea/index.ts initRepo, calls helper.isOrg → GET /orgs/{owner} and rethrows on error. Gitea 1.24.3, routers/api/v1/api.go, puts /orgs/{org} behind tokenRequiresScopes(AccessTokenScopeCategoryOrganization).

Why write:issue is needed next. In the same api.go, /repos/{o}/{r}/issues…, /labels and /milestones sit in a group with tokenRequiresScopes(AccessTokenScopeCategoryIssue) (lines 1466–1582). Renovate's gitea-helper uses /issues, /issues/{n}/labels, /labels and /issues/{n}/comments, for the dependency dashboard (:dependencyDashboard is extended) and for PR labels. A dry run only reads them; a scheduled run writes them.

The other causes the coordinator listed are ruled out:

  • Schedule window and dry-run. The run never reached branch processing.
  • Repository config. The run never reached the clone ("cloned": undefined).
  • The Summary step. It is not involved; the failure is Renovate's own exit 8.

What I verified

  • I read the whole run 350 job log (204 lines), and the router lines above. Runs 348 and 349 are as recorded in decisions/2026-10-30-renovate-first-run.md's follow-up: /user got 403 from tokenRequiresScopes.
  • The probe step, in the pinned image. I extracted the step body from the YAML and ran it in ghcr.io/renovatebot/renovate:44.167.0 (curl 8.5.0 and jq are present) with a deliberately bogus token. Every probe reported 401 with the "token VALUE" diagnosis, the scope-list read fell back to a warning, and the step exited 1.
  • The scope-list parser. Against run 350's exact message format it reports write:issue MISSING. Against a five-scope list it reports both OK. Against all it reports both OK.
  • The lint guard. It flags renovatebot/github-action in the pre-#84 renovate.yaml (dfc432c, line 152). It reports nothing on the current workflows, and it ignores commented lines. bash -n ci/lint.sh passes.
  • Account facts, read-only. renovate-bot is a collaborator with write, and the org is public.

What I did NOT verify

  • The probe with a real token. UNVERIFIED. I never had the token. In particular, I have not observed that GET /api/v1/admin/orgs gives renovate-bot's token a 403 with a token scope= list. The source says the scope check runs before reqSiteAdmin. If the message format differs, the step warns and does not fail.
  • That five scopes are enough for a full non-dry run. They cover every helper endpoint Renovate 44.167.0 calls, mapped to Gitea 1.24.3's route categories by reading source. Branch pushes go over git-over-HTTPS with the token; I believe that needs write:repository, but I have not observed it.
  • Registry reads as renovate-bot. read:package on a public org is expected to work. The new probe is the first real test of it.
  • Sensitive paths. .gitea/** and ci/** are both sensitive. CI's approval-policy will block until the operator posts /approve-override as a PR review.