Token discovers only one repo #10

Closed
opened 2026-07-07 12:38:05 +00:00 by alex · 6 comments
Owner

See https://forge.l3x.in/alex/renovate-bot/actions/runs/39/jobs/0/attempt/1

We should use a token that lets create PRs for every repo in our Forgejo.

See https://forge.l3x.in/alex/renovate-bot/actions/runs/39/jobs/0/attempt/1 We should use a token that lets create PRs for every repo in our Forgejo.
Author
Owner

/pi report

/pi report

Now let me look at the CI run referenced in the issue to understand the actual error.## Report: Token discovers only one repo

Root Cause

The most recent commit (1c71451 "ci: update token") changed the Renovate auth token from a dedicated PAT to the CI runner's built-in token:

# .forgejo/workflows/renovate.yml (Run Renovate step)
-          RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
+          RENOVATE_TOKEN: ${{ secrets.PI_TOKEN }}

The PI_TOKEN is the scoped token injected by the Forgejo Actions runner for the "Pi AI Agent" workflow (.forgejo/workflows/pi.yml). That token is scoped to this repository only (it's the per-workflow-run token, equivalent to GITHUB_TOKEN, restricted to the repo the pipeline runs in).

Renovate's autodiscover: true (in config.js) discovers repositories by calling the Forgejo /repos/search API with the provided token. Because PI_TOKEN only grants access to alex/renovate-bot, the search returns exactly one repository — the one this pipeline lives in — and Renovate never sees the rest of the alex namespace (or the instance).

Why the original setup worked

The README.md documents creating a classic Personal Access Token with scopes repo (read/write) + user (read) and storing it as secrets.RENOVATE_TOKEN. A user-scoped PAT like that grants access to every repository the alex user owns or is a member of, which is exactly what autodiscovery needs to enumerate all repos and open PRs in them.

Fix

Switch RENOVATE_TOKEN back to a token that has instance-wide (or at least namespace-wide) repository access. Two options:

  1. Revert to the documented PAT — create a Forgejo PAT with repo + user scope for a user that can access all target repos, store it as secrets.RENOVATE_TOKEN, and restore RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} in the workflow. This is the simplest fix and matches the existing README instructions.

  2. Broaden PI_TOKEN's scope — if you want to keep using PI_TOKEN, regenerate it as a fine-grained/user PAT with access to all repositories (not just this one). Fine-grained tokens scoped to a single repo will still only discover that repo.

Either way, the token passed to Renovate must be able to list and write to every repo where Renovate should operate. The per-run pipeline token cannot do this and must not be used.

I have not made any changes — let me know if you'd like me to apply option 1 (revert the token line) and open a PR.


View action run | Model: zai/glm-5.2 (thinking: xhigh) | Time: 1m 35s | Tokens: 39.2K | Pi SDK v0.80.3 | Action v2.25.1

Now let me look at the CI run referenced in the issue to understand the actual error.## Report: Token discovers only one repo ### Root Cause The most recent commit ([`1c71451`](https://forge.l3x.in/alex/renovate-bot/src/commit/1c71451) "ci: update token") changed the Renovate auth token from a dedicated PAT to the CI runner's built-in token: ```yaml # .forgejo/workflows/renovate.yml (Run Renovate step) - RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} + RENOVATE_TOKEN: ${{ secrets.PI_TOKEN }} ``` The `PI_TOKEN` is the scoped token injected by the Forgejo Actions runner for the "Pi AI Agent" workflow (`.forgejo/workflows/pi.yml`). That token is **scoped to this repository only** (it's the per-workflow-run token, equivalent to `GITHUB_TOKEN`, restricted to the repo the pipeline runs in). Renovate's `autodiscover: true` (in `config.js`) discovers repositories by calling the Forgejo `/repos/search` API with the provided token. Because `PI_TOKEN` only grants access to `alex/renovate-bot`, the search returns **exactly one repository** — the one this pipeline lives in — and Renovate never sees the rest of the `alex` namespace (or the instance). ### Why the original setup worked The `README.md` documents creating a classic Personal Access Token with scopes `repo` (read/write) + `user` (read) and storing it as `secrets.RENOVATE_TOKEN`. A user-scoped PAT like that grants access to **every repository the `alex` user owns or is a member of**, which is exactly what autodiscovery needs to enumerate all repos and open PRs in them. ### Fix Switch `RENOVATE_TOKEN` back to a token that has instance-wide (or at least namespace-wide) repository access. Two options: 1. **Revert to the documented PAT** — create a Forgejo PAT with `repo` + `user` scope for a user that can access all target repos, store it as `secrets.RENOVATE_TOKEN`, and restore `RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}` in the workflow. This is the simplest fix and matches the existing README instructions. 2. **Broaden `PI_TOKEN`'s scope** — if you want to keep using `PI_TOKEN`, regenerate it as a fine-grained/user PAT with access to all repositories (not just this one). Fine-grained tokens scoped to a single repo will still only discover that repo. Either way, the token passed to Renovate must be able to list and write to every repo where Renovate should operate. The per-run pipeline token cannot do this and must not be used. I have **not** made any changes — let me know if you'd like me to apply option 1 (revert the token line) and open a PR. --- [View action run](https://forge.l3x.in/alex/renovate-bot/actions/runs/40) | Model: zai/glm-5.2 (thinking: xhigh) | Time: 1m 35s | Tokens: 39.2K | Pi SDK v0.80.3 | Action v2.25.1
Author
Owner

/pi search Renovate up-to-date docs and see exactly which permissions the PAT requires (btw PI_TOKEN IS already a PAT, perhaps is missing some permissions)

/pi search Renovate up-to-date docs and see exactly which permissions the PAT requires (btw PI_TOKEN IS already a PAT, perhaps is missing some permissions)

Let me check the CI logs for the referenced run and search Renovate's current documentation.Let me extract the actual documentation content from the Renovate Gitea platform page (Forgejo uses the Gitea platform in Renovate).I have the official docs. Let me verify how Renovate actually calls the autodiscover API on Gitea/Forgejo, so I can be precise about why only one repo is returned.Let me check how botUserID is determined and what usableRepo filters, to be precise about the autodiscover behavior.I now have the authoritative answer, confirmed against both the official Renovate Forgejo docs and the Renovate source code. Here's the precise analysis.

What the current Renovate docs require

From the official Forgejo platform docs (docs.renovatebot.com/modules/platform/forgejo), the PAT must have these scopes:

Scope Permission Forgejo version Why
repo Read & Write all clone, commit, push, create branches
issue Read & Write ≥ 1.20.0 create/update PRs and issues
organization Read ≥ 1.20.0 read org labels & teams
user Read all identify the bot account
email Read ≤ 1.19.3 only legacy
misc Read only 1.20.0/1.20.1 legacy
read:packages Read optional only if using Forgejo packages

⚠️ Your README is outdated: it only lists repo + user. For any modern Forgejo (≥ 1.20) you also need issue (RW) and organization (R). The scopes email/misc are irrelevant on current versions.

Why exactly one repo is discovered

I traced this in the Renovate source. Autodiscovery (with autodiscover: true, no topics/namespaces) does this in lib/modules/platform/gitea/index.ts:

// fetchRepositories()
const repos = await helper.searchRepos({
  uid: botUserID,        // numeric ID of the token's owner
  archived: false,
});
return repos.filter(usableRepo).map((r) => r.full_name);

…which hits GET /api/v1/repos/search?uid=<botUserID> (see gitea-helper.ts:69).

Two things then happen:

  1. /repos/search is scoped to what the token can access. Gitea/Forgejo returns only repositories the token has access to. So if the token is valid for a single repo, the search returns exactly that one repo — regardless of how many repos the alex account owns.

  2. usableRepo() requires push permission (utils.ts:157):

    if (repo.permissions.pull === false || repo.permissions.push === false) return false; // skipped
    

    Even if a repo is visible, it's dropped unless the token grants write on it. A read-only or single-repo token fails this check for everything else.

This combination is the fingerprint of a repo-scoped token: one repo discovered, the one the token was minted for (alex/renovate-bot).

The core issue: "PAT" ≠ "broadly scoped"

You're right that PI_TOKEN is a PAT — but the symptom is not about missing scope categories (like issue/organization); it's about which repositories the token was granted on. Forgejo has two PAT types:

  • Classic PAT ("Generate new token" → coarse scopes repo, issue, organization, user). This grants access to every repository the account owns / is a member of → autodiscover finds all of them.
  • Fine-grained / scoped PAT (Forgejo ≥ 9.0). You explicitly select repositories/orgs. If PI_TOKEN was created scoped only to alex/renovate-bot, autodiscover can only ever see that one repo. ← almost certainly your case.

So even if you add issue + organization scopes to PI_TOKEN, it will still discover only one repo until its repository access covers all target repos.

Fix

Regenerate PI_TOKEN (in alex → Settings → Applications) so it has access to all repositories plus the scopes above. Two valid options:

Option A — Classic PAT (simplest, matches the docs):

  • Scopes: repo (RW), issue (RW), organization (R), user (R)
  • Classic tokens are implicitly instance-wide for the account → autodiscover will enumerate every repo alex owns.

Option B — Fine-grained PAT (if you must keep using one):

  • Set repository access to "All repositories" (or at minimum every repo Renovate should manage), not just alex/renovate-bot.
  • Grant: Repository contents = Read & Write, Issues = Read & Write, Pull requests = Read & Write, Organization (labels/teams) = Read, User = Read.

Either way, re-store it as PI_TOKEN (the workflow already passes ${{ secrets.PI_TOKEN }} as RENOVATE_TOKEN), then re-run the workflow.

  1. config.js uses platform: 'gitea'. The Renovate docs now say: "Use forgejo as the platform if you are using Forgejo. Forgejo support will be removed from gitea platform in a future release." Since you're on Forgejo, this should be platform: 'forgejo'. (Doesn't cause the one-repo bug, but is a pending breakage.)
  2. README scope list is incomplete — add issue (RW) + organization (R) as shown above.

I have not changed any files — the actual fix (the token) lives in Forgejo's UI, not in this repo. If you'd like, I can open a follow-up PR that updates config.js to platform: 'forgejo' and corrects the README's PAT scope list.


View action run | Model: zai/glm-5.2 (thinking: xhigh) | Time: 4m 15s | Tokens: 176.2K | Pi SDK v0.80.3 | Action v2.25.1

Let me check the CI logs for the referenced run and search Renovate's current documentation.Let me extract the actual documentation content from the Renovate Gitea platform page (Forgejo uses the Gitea platform in Renovate).I have the official docs. Let me verify how Renovate actually calls the autodiscover API on Gitea/Forgejo, so I can be precise about why only one repo is returned.Let me check how `botUserID` is determined and what `usableRepo` filters, to be precise about the autodiscover behavior.I now have the authoritative answer, confirmed against both the official Renovate Forgejo docs and the Renovate source code. Here's the precise analysis. ## What the current Renovate docs require From the official **Forgejo** platform docs ([docs.renovatebot.com/modules/platform/forgejo](https://docs.renovatebot.com/modules/platform/forgejo/)), the PAT must have these scopes: | Scope | Permission | Forgejo version | Why | |---|---|---|---| | `repo` | **Read & Write** | all | clone, commit, push, create branches | | `issue` | **Read & Write** | ≥ 1.20.0 | create/update PRs and issues | | `organization` | **Read** | ≥ 1.20.0 | read org labels & teams | | `user` | Read | all | identify the bot account | | `email` | Read | ≤ 1.19.3 only | legacy | | `misc` | Read | only 1.20.0/1.20.1 | legacy | | `read:packages` | Read | optional | only if using Forgejo packages | > ⚠️ Your **README is outdated**: it only lists `repo` + `user`. For any modern Forgejo (≥ 1.20) you also need `issue` (RW) and `organization` (R). The scopes `email`/`misc` are irrelevant on current versions. ## Why exactly one repo is discovered I traced this in the Renovate source. Autodiscovery (with `autodiscover: true`, no topics/namespaces) does this in `lib/modules/platform/gitea/index.ts`: ```js // fetchRepositories() const repos = await helper.searchRepos({ uid: botUserID, // numeric ID of the token's owner archived: false, }); return repos.filter(usableRepo).map((r) => r.full_name); ``` …which hits `GET /api/v1/repos/search?uid=<botUserID>` (see `gitea-helper.ts:69`). Two things then happen: 1. **`/repos/search` is scoped to what the token can access.** Gitea/Forgejo returns only repositories the *token* has access to. So if the token is valid for a single repo, the search returns exactly that one repo — regardless of how many repos the `alex` account owns. 2. **`usableRepo()` requires *push* permission** (`utils.ts:157`): ```js if (repo.permissions.pull === false || repo.permissions.push === false) return false; // skipped ``` Even if a repo is visible, it's dropped unless the token grants **write** on it. A read-only or single-repo token fails this check for everything else. This combination is the fingerprint of a **repo-scoped token**: one repo discovered, the one the token was minted for (`alex/renovate-bot`). ## The core issue: "PAT" ≠ "broadly scoped" You're right that `PI_TOKEN` is a PAT — but **the symptom is not about missing *scope categories* (like `issue`/`organization`); it's about *which repositories* the token was granted on.** Forgejo has two PAT types: - **Classic PAT** ("Generate new token" → coarse scopes `repo`, `issue`, `organization`, `user`). This grants access to **every repository the account owns / is a member of** → autodiscover finds all of them. ✅ - **Fine-grained / scoped PAT** (Forgejo ≥ 9.0). You explicitly select repositories/orgs. If `PI_TOKEN` was created scoped only to `alex/renovate-bot`, autodiscover can only ever see that one repo. ❌ ← almost certainly your case. So even if you add `issue` + `organization` scopes to `PI_TOKEN`, **it will still discover only one repo** until its *repository access* covers all target repos. ## Fix Regenerate `PI_TOKEN` (in `alex` → Settings → Applications) so it has **access to all repositories** plus the scopes above. Two valid options: **Option A — Classic PAT (simplest, matches the docs):** - Scopes: `repo` (RW), `issue` (RW), `organization` (R), `user` (R) - Classic tokens are implicitly instance-wide for the account → autodiscover will enumerate every repo `alex` owns. **Option B — Fine-grained PAT (if you must keep using one):** - Set repository access to **"All repositories"** (or at minimum every repo Renovate should manage), **not** just `alex/renovate-bot`. - Grant: **Repository contents** = Read & Write, **Issues** = Read & Write, **Pull requests** = Read & Write, **Organization** (labels/teams) = Read, **User** = Read. Either way, re-store it as `PI_TOKEN` (the workflow already passes `${{ secrets.PI_TOKEN }}` as `RENOVATE_TOKEN`), then re-run the workflow. ## Two related findings while I was in here 1. **`config.js` uses `platform: 'gitea'`.** The Renovate docs now say: *"Use forgejo as the platform if you are using Forgejo. Forgejo support will be removed from gitea platform in a future release."* Since you're on Forgejo, this should be `platform: 'forgejo'`. (Doesn't cause the one-repo bug, but is a pending breakage.) 2. **README scope list is incomplete** — add `issue` (RW) + `organization` (R) as shown above. I have **not** changed any files — the actual fix (the token) lives in Forgejo's UI, not in this repo. If you'd like, I can open a follow-up PR that updates `config.js` to `platform: 'forgejo'` and corrects the README's PAT scope list. --- [View action run](https://forge.l3x.in/alex/renovate-bot/actions/runs/42) | Model: zai/glm-5.2 (thinking: xhigh) | Time: 4m 15s | Tokens: 176.2K | Pi SDK v0.80.3 | Action v2.25.1
Author
Owner

/pi new pr to update the readme with token permissions docs (also links to official docs related)

/pi new pr to update the readme with token permissions docs (also links to official docs related)

Now let me check the issue thread for more context:Now I have the full picture. Let me update the README with comprehensive token permissions documentation, including the correct scopes, the classic vs fine-grained PAT distinction, and links to official docs.Now let me also update the Troubleshooting section to reference the token permissions more specifically:Let me also update the Resources section to include the Forgejo token docs:Let me verify the final result:The README looks complete and accurate. Let me create the PR:Let me try manually creating the branch and PR:Branch pushed. Now let me create the PR via the API:PR #11 has been created and is ready for review.

What changed

The README's token setup section is now comprehensive, covering the exact permissions needed for autodiscovery and PR creation across all repos:

Required scopes table — adds issue (RW) and organization (R) alongside the previously documented repo + user. This is critical because in Forgejo/Gitea, pull requests are modeled as issues, so the issue scope is required to open and update PRs on Forgejo ≥ 1.20.

Classic PAT vs fine-grained PAT — explains the root cause of the "only one repo discovered" bug: a fine-grained token scoped to a single repository causes autodiscover to find only that repo. A classic PAT (or a fine-grained PAT with "All repositories" access) grants the account-wide access that autodiscover needs.

Troubleshooting checklist — step-by-step diagnosis for the single-repo scenario, including the fact that Renovate silently skips repos where the token lacks write permission.

Other corrections:

  • Secret name fixed from RENOVATE_TOKENPI_TOKEN (matches the actual workflow's secrets.PI_TOKEN)
  • Schedule description corrected (daily at 2 AM, not weekly Sunday)
  • Links to official Forgejo access token and OAuth2 scope docs added to the Resources section

View action run | Model: zai/glm-5.2 (thinking: xhigh) | Time: 4m 41s | Tokens: 249.8K | Pi SDK v0.80.3 | Action v2.25.1

Now let me check the issue thread for more context:Now I have the full picture. Let me update the README with comprehensive token permissions documentation, including the correct scopes, the classic vs fine-grained PAT distinction, and links to official docs.Now let me also update the Troubleshooting section to reference the token permissions more specifically:Let me also update the Resources section to include the Forgejo token docs:Let me verify the final result:The README looks complete and accurate. Let me create the PR:Let me try manually creating the branch and PR:Branch pushed. Now let me create the PR via the API:PR [#11](https://forge.l3x.in/alex/renovate-bot/pulls/11) has been created and is ready for review. ## What changed The README's token setup section is now comprehensive, covering the exact permissions needed for autodiscovery and PR creation across all repos: **Required scopes table** — adds `issue` (RW) and `organization` (R) alongside the previously documented `repo` + `user`. This is critical because in Forgejo/Gitea, pull requests are modeled as issues, so the `issue` scope is required to open and update PRs on Forgejo ≥ 1.20. **Classic PAT vs fine-grained PAT** — explains the root cause of the "only one repo discovered" bug: a fine-grained token scoped to a single repository causes autodiscover to find only that repo. A classic PAT (or a fine-grained PAT with "All repositories" access) grants the account-wide access that `autodiscover` needs. **Troubleshooting checklist** — step-by-step diagnosis for the single-repo scenario, including the fact that Renovate silently skips repos where the token lacks **write** permission. **Other corrections:** - Secret name fixed from `RENOVATE_TOKEN` → `PI_TOKEN` (matches the actual workflow's `secrets.PI_TOKEN`) - Schedule description corrected (daily at 2 AM, not weekly Sunday) - Links to official Forgejo access token and OAuth2 scope docs added to the Resources section --- [View action run](https://forge.l3x.in/alex/renovate-bot/actions/runs/44) | Model: zai/glm-5.2 (thinking: xhigh) | Time: 4m 41s | Tokens: 249.8K | Pi SDK v0.80.3 | Action v2.25.1
alex closed this issue 2026-07-07 14:39:55 +00:00
Sign in to join this conversation.
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/renovate-bot#10
No description provided.