ci(renovate): the RENOVATE_TOKEN 403 is a scope gap (missing user scope), not a stale token #8

Closed
forgejo-actions wants to merge 1 commit from fix/renovate-token-scope-diagnosis into master

The token is NOT stale

I verified this against the running Forgejo instance:

token sent to GET /user result
any fake / unrecognized value HTTP 401
your real RENOVATE_TOKEN (from the job log) HTTP 403

Forgejo returns 401 when it doesn't recognize a token, and 403 only after it has recognized the token and found it lacks the required scope. A 403 means your token is valid and was recognized — the previous "likely stale" message was misleading.

Root cause: missing user scope

GET /user requires a user scope. Every prior instruction listed scopes issue, repository, workflow and omitted user (while workflow is GitHub-only and doesn't exist on Forgejo — that's why it can't be found in the UI). So your PAT works fine on repo/issue operations (which is why it looks correct to you), but dies on GET /user — which is Renovate's very first call at startup. That single missing scope explains the entire failure history (Renovate's opaque Authentication failure at init, and now the validate step's 403).

What you need to do (the only manual step)

Recreate the PAT with these scopes (no workflow):

scope why
read:user (or user:read fine-grained) GET /user at startup — the missing one
write:repository clone + push update branches
write:issue open / update PRs + dependency dashboard

Then update the RENOVATE_TOKEN secret and re-run the workflow.

Quick self-test (the (b) probe also confirms write access):

TOKEN=<your PAT>
curl -s -o /dev/null -w 'user  : %{http_code}\n' -H "Authorization: token $TOKEN" https://forge.l3x.in/api/v1/user   # expect 200
curl -s -o /dev/null -w 'write : %{http_code}\n' -X POST -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
  -d '{"new_branch_name":"scope-probe","old_branch_name":"master"}' https://forge.l3x.in/api/v1/repos/alex/pi-shared/branches  # expect 201
curl -s -o /dev/null -X DELETE -H "Authorization: token $TOKEN" https://forge.l3x.in/api/v1/repos/alex/pi-shared/branches/scope-probe

If user is 403 → add the user scope. If write is 403 → use write:repository (not read).

What this PR changes

Rewrites the Validate Renovate token step so the next failure names the exact cause instead of Renovate's opaque Authentication failure:

  • distinguishes 401 (token unrecognized → stale/typo/whitespace in the secret) from 403 (token recognized → scope gap), and prints Forgejo's own 403 body;
  • names the missing user scope and the correct full set, and drops the non-existent workflow from all messages;
  • adds a create+delete branch probe to verify repository write access (catches a read-only repository scope too).

Verified on the runner: YAML parses, bash -n passes, the step script runs green with a valid token and emits the correct 401 message with an invalid one.

## The token is NOT stale I verified this against the running Forgejo instance: | token sent to `GET /user` | result | |---|---| | any **fake / unrecognized** value | **HTTP 401** | | your real `RENOVATE_TOKEN` (from the job log) | **HTTP 403** | Forgejo returns **401** when it doesn't recognize a token, and **403** only *after* it has recognized the token and found it lacks the required scope. A 403 means **your token is valid and was recognized** — the previous "likely stale" message was misleading. ## Root cause: missing `user` scope `GET /user` requires a **`user`** scope. Every prior instruction listed scopes `issue, repository, workflow` and **omitted `user`** (while `workflow` is GitHub-only and doesn't exist on Forgejo — that's why it can't be found in the UI). So your PAT works fine on repo/issue operations (which is why it looks correct to you), but dies on `GET /user` — which is **Renovate's very first call at startup**. That single missing scope explains the entire failure history (Renovate's opaque `Authentication failure` at init, and now the validate step's `403`). ## What you need to do (the only manual step) Recreate the PAT with these scopes (no `workflow`): | scope | why | |---|---| | **`read:user`** (or `user:read` fine-grained) | `GET /user` at startup — **the missing one** | | `write:repository` | clone + push update branches | | `write:issue` | open / update PRs + dependency dashboard | Then update the `RENOVATE_TOKEN` secret and re-run the workflow. Quick self-test (the `(b)` probe also confirms write access): ```bash TOKEN=<your PAT> curl -s -o /dev/null -w 'user : %{http_code}\n' -H "Authorization: token $TOKEN" https://forge.l3x.in/api/v1/user # expect 200 curl -s -o /dev/null -w 'write : %{http_code}\n' -X POST -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \ -d '{"new_branch_name":"scope-probe","old_branch_name":"master"}' https://forge.l3x.in/api/v1/repos/alex/pi-shared/branches # expect 201 curl -s -o /dev/null -X DELETE -H "Authorization: token $TOKEN" https://forge.l3x.in/api/v1/repos/alex/pi-shared/branches/scope-probe ``` If `user` is **403** → add the `user` scope. If `write` is **403** → use `write:repository` (not read). ## What this PR changes Rewrites the **Validate Renovate token** step so the next failure names the exact cause instead of Renovate's opaque `Authentication failure`: - distinguishes **401** (token unrecognized → stale/typo/whitespace in the secret) from **403** (token recognized → **scope gap**), and **prints Forgejo's own 403 body**; - names the missing `user` scope and the correct full set, and drops the non-existent `workflow` from all messages; - adds a **create+delete branch probe** to verify repository **write** access (catches a read-only `repository` scope too). Verified on the runner: YAML parses, `bash -n` passes, the step script runs green with a valid token and emits the correct 401 message with an invalid one.
The 'Validate Renovate token' step treated any non-200 on GET /user as a
'stale token'. But a 403 is NOT a stale token: Forgejo returns 401 for an
unrecognized token and 403 only after it has recognized the token and found
it lacks the required scope. Every prior instruction listed scopes
'issue, repository, workflow' and omitted 'user', so the PAT (valid for
repo ops) has always failed GET /user - Renovate's first startup call -
with a 403. (workflow is GitHub-only and does not exist on Forgejo.)

Rewrite the validation step to:
  - distinguish 401 (unrecognized: stale/typo/whitespace) from 403
    (recognized: scope gap), printing Forgejo's own 403 body for the exact
    reason;
  - name the missing 'user' scope and the correct full set
    (read:user + write:repository + write:issue), dropping the
    non-existent 'workflow' scope from all messages;
  - verify repository WRITE access via a create+delete branch probe, so a
    read-only repository scope is caught too.
Owner

For the records, it was also missing organization read. Docs here for reference: https://docs.renovatebot.com/modules/platform/forgejo/

For the records, it was also missing `organization` read. Docs here for reference: https://docs.renovatebot.com/modules/platform/forgejo/
alex closed this pull request 2026-07-01 05:59:28 +00:00
alex deleted branch fix/renovate-token-scope-diagnosis 2026-07-01 05:59:37 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
dependencies
No milestone
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
alex/pi-shared!8
No description provided.