The token fix worked: platform init now passes. Renovate then stopped at the next missing scope. 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
From the job log:
ERROR: Repository has unknown error
"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"
This is Gitea, not Cilium. The body is Gitea's JSON, Gitea logged the request, and x-envoy-upstream-service-time shows Envoy forwarded it.
The secret was updated./user went from 403 to 200, and the scope list now includes read:user.
Why read:organization. Renovate's initRepo → isOrg → GET /orgs/{owner} requires it.
Why write:issue is the next wall. Gitea 1.24.3 files /issues, /labels and /milestones under the issue scope, which the dependency dashboard and PR labels need.
The cause of the loop. The workflow header listed only three scopes, and the operator minted exactly those.
Operator fix (required, the main fix)
Re-mint RENOVATE_TOKEN for renovate-bot with user: Read, organization: Read, repository: Read and Write, issue: Read and Write, package: Read. Overwrite the Actions secret and revoke the old token.
Git changes
renovate.yaml:
The scope list is corrected in the header and the preflight text, with the call each scope serves.
A new preflight step probes the scopes with GETs, the token passed to curl on stdin and never in argv. It names a missing scope instead of Renovate's bare Authentication failure, and tells a 401 (value) from a Gitea 403 (scope) from an Envoy Access denied (Cilium).
ci/lint.sh: new guard. It rejects workflow uses: lines that need a Docker daemon (docker://, renovatebot/github-action, docker/*-action), the class fixed in #84. It flags the pre-#84 workflow, passes the current tree and ignores comments.
Handoff: decisions/2026-10-30-renovate-auth.md.
Gate
The local toolbox gate returned rc 3 (UNDETERMINED, not green). gitea-settings is the only stage that did not run. .gitea/** and ci/** are sensitive paths, so approval-policy will fail in CI until the operator posts /approve-override <reason> as a PR review. No agent posted it.
The probe was tested only with a bogus token, which gives the 401 path, plus a scope-list parser test against run 350's real message. The re-minted token does not depend on this PR: it can be dispatched against main right away.
## Root cause (run 350)
The token fix worked: platform init now passes. Renovate then stopped at the **next** missing scope. 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
```
From the job log:
```
ERROR: Repository has unknown error
"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"
```
- **This is Gitea, not Cilium.** The body is Gitea's JSON, Gitea logged the request, and `x-envoy-upstream-service-time` shows Envoy forwarded it.
- **The secret was updated.** `/user` went from 403 to 200, and the scope list now includes `read:user`.
- **Why `read:organization`.** Renovate's `initRepo` → `isOrg` → `GET /orgs/{owner}` requires it.
- **Why `write:issue` is the next wall.** Gitea 1.24.3 files `/issues`, `/labels` and `/milestones` under the **issue** scope, which the dependency dashboard and PR labels need.
- **The cause of the loop.** The workflow header listed only three scopes, and the operator minted exactly those.
## Operator fix (required, the main fix)
Re-mint `RENOVATE_TOKEN` for `renovate-bot` with **user: Read, organization: Read, repository: Read and Write, issue: Read and Write, package: Read**. Overwrite the Actions secret and revoke the old token.
## Git changes
- `renovate.yaml`:
- The scope list is corrected in the header and the preflight text, with the call each scope serves.
- A **new preflight step probes the scopes** with GETs, the token passed to curl on stdin and never in argv. It names a missing scope instead of Renovate's bare `Authentication failure`, and tells a 401 (value) from a Gitea 403 (scope) from an Envoy `Access denied` (Cilium).
- `ci/lint.sh`: new guard. It rejects workflow `uses:` lines that need a Docker daemon (`docker://`, `renovatebot/github-action`, `docker/*-action`), the class fixed in #84. It flags the pre-#84 workflow, passes the current tree and ignores comments.
- Handoff: `decisions/2026-10-30-renovate-auth.md`.
## Gate
The local toolbox gate returned **rc 3** (UNDETERMINED, not green). `gitea-settings` is the only stage that did not run. `.gitea/**` and `ci/**` are sensitive paths, so **`approval-policy` will fail in CI** until the operator posts `/approve-override <reason>` as a PR **review**. No agent posted it.
The probe was tested only with a bogus token, which gives the 401 path, plus a scope-list parser test against run 350's real message. The re-minted token does not depend on this PR: it can be dispatched against `main` right away.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Root cause (run 350)
The token fix worked: platform init now passes. Renovate then stopped at the next missing scope. Gitea's router log, filtered to the act-runner pod
10.0.0.38:From the job log:
x-envoy-upstream-service-timeshows Envoy forwarded it./userwent from 403 to 200, and the scope list now includesread:user.read:organization. Renovate'sinitRepo→isOrg→GET /orgs/{owner}requires it.write:issueis the next wall. Gitea 1.24.3 files/issues,/labelsand/milestonesunder the issue scope, which the dependency dashboard and PR labels need.Operator fix (required, the main fix)
Re-mint
RENOVATE_TOKENforrenovate-botwith user: Read, organization: Read, repository: Read and Write, issue: Read and Write, package: Read. Overwrite the Actions secret and revoke the old token.Git changes
renovate.yaml:Authentication failure, and tells a 401 (value) from a Gitea 403 (scope) from an EnvoyAccess denied(Cilium).ci/lint.sh: new guard. It rejects workflowuses:lines that need a Docker daemon (docker://,renovatebot/github-action,docker/*-action), the class fixed in #84. It flags the pre-#84 workflow, passes the current tree and ignores comments.decisions/2026-10-30-renovate-auth.md.Gate
The local toolbox gate returned rc 3 (UNDETERMINED, not green).
gitea-settingsis the only stage that did not run..gitea/**andci/**are sensitive paths, soapproval-policywill fail in CI until the operator posts/approve-override <reason>as a PR review. No agent posted it.The probe was tested only with a bogus token, which gives the 401 path, plus a scope-list parser test against run 350's real message. The re-minted token does not depend on this PR: it can be dispatched against
mainright away.🤖 Generated with Claude Code