--- date: 2026-10-30 branch: fix/renovate-auth title: 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/*-action`s. 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.