fix(forgejo): correct which endpoint the repository lock actually closes #672

Merged
binjovi-bot merged 1 commit from fix/document-the-reachable-creation-path into trunk 2026-09-12 13:33:08 +00:00
Owner

Correcting the reason, not the fix

PR #670 is deployed and works. But it named the wrong endpoint, and a security control documented with the wrong mechanism is a trap for the next reader.

What #670 said: the contributor token holds write:repository, and that authorizes POST /user/repos.

What the live server says:

POST /user/repos    -> 403 {"message":"token does not have at least one of
                            required scope(s): [write:user]"}

The routine token never had that route. The reachable one is POST /repos/migraterouters/api/v1/api.go:777 marks it // (repo scope), and its group closes with tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository) at line 1083.

That also fits how sean/ex_mcp appeared: a full upstream import carrying 29 tags and upstream branches (cursor/…, claude/…). That is what a migrate produces, not what a bare create produces.

The fix was right, and is now proven live

POST /repos/migrate, contributor token, after the deploy:
  HTTP 422 {"message":"You have already reached your limit of 0 repositories."}
  repo absent (HTTP 404) -- nothing was created

That message is ErrReachLimitOfRepo, raised at services/repository/create.go:201 — the exact line the guard header cites. migrate.go:180 calls CreateRepositoryDirectly(ctx, ctx.Doer(), repoOwner, …), and for a contributor doer is non-admin, so the limit applies.

What the limit does not stop

Now stated plainly in the guard header, because it was implied before and should not be:

  • A site admin bypasses the limit by design. make forgejo-cred-admin can still create an undeclared repository. That is the break-glass path, and it is exactly why tests/forgejo-repo-reconcile/run-all.sh exists. The limit closes the routine path; the reconciler catches anything that arrives by any path.
  • The organization setting is not live-proven. POST /orgs is refused for want of write:organization before DISABLE_REGULAR_ORG_CREATION is ever consulted. It stays as defence in depth for any wider token, and is now labelled as such rather than claimed as verified.

Documentation only — no behaviour change. tests/forgejo-repo-creation-locked.sh still passes, and still fails when either setting is removed.

https://claude.ai/code/session_01XdBRc9CHgFhAxCyHbdVtt6

## Correcting the reason, not the fix PR #670 is deployed and works. But it named the wrong endpoint, and a security control documented with the wrong mechanism is a trap for the next reader. **What #670 said:** the contributor token holds `write:repository`, and that authorizes `POST /user/repos`. **What the live server says:** ``` POST /user/repos -> 403 {"message":"token does not have at least one of required scope(s): [write:user]"} ``` The routine token never had that route. The reachable one is `POST /repos/migrate` — `routers/api/v1/api.go:777` marks it `// (repo scope)`, and its group closes with `tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)` at line 1083. That also fits how `sean/ex_mcp` appeared: a full upstream import carrying 29 tags and upstream branches (`cursor/…`, `claude/…`). That is what a migrate produces, not what a bare create produces. ## The fix was right, and is now proven live ``` POST /repos/migrate, contributor token, after the deploy: HTTP 422 {"message":"You have already reached your limit of 0 repositories."} repo absent (HTTP 404) -- nothing was created ``` That message is `ErrReachLimitOfRepo`, raised at `services/repository/create.go:201` — the exact line the guard header cites. `migrate.go:180` calls `CreateRepositoryDirectly(ctx, ctx.Doer(), repoOwner, …)`, and for a contributor `doer` is non-admin, so the limit applies. ## What the limit does not stop Now stated plainly in the guard header, because it was implied before and should not be: - **A site admin bypasses the limit by design.** `make forgejo-cred-admin` can still create an undeclared repository. That is the break-glass path, and it is exactly why `tests/forgejo-repo-reconcile/run-all.sh` exists. The limit closes the routine path; the reconciler catches anything that arrives by any path. - **The organization setting is not live-proven.** `POST /orgs` is refused for want of `write:organization` before `DISABLE_REGULAR_ORG_CREATION` is ever consulted. It stays as defence in depth for any wider token, and is now labelled as such rather than claimed as verified. Documentation only — no behaviour change. `tests/forgejo-repo-creation-locked.sh` still passes, and still fails when either setting is removed. https://claude.ai/code/session_01XdBRc9CHgFhAxCyHbdVtt6
fix(forgejo): correct which endpoint the repository lock actually closes
All checks were successful
binjovi/ci Binjovi completed the frozen plan
d1c2bbffcb
The previous entry said the contributor token creates repositories through
POST /user/repos. Measured against the live server, that route answers
403 "token does not have at least one of required scope(s): [write:user]".
The routine token holds write:repository only, so it never had that route.

The reachable route is POST /repos/migrate. routers/api/v1/api.go:777 marks
it "(repo scope)", and its group closes with
tokenRequiresScopes(AccessTokenScopeCategoryRepository) at line 1083. That
also matches how sean/ex_mcp appeared: a full upstream import carrying 29
tags and upstream branches, which is what migrate produces.

So the fix was right and the reason was wrong. Proven live after the deploy:
POST /repos/migrate with the contributor token returns 422 "You have already
reached your limit of 0 repositories." and creates nothing. That message is
ErrReachLimitOfRepo from services/repository/create.go:201, the exact line
the guard header cites.

The header now also states what the limit does NOT stop. A site admin
bypasses it by design, so `make forgejo-cred-admin` can still create an
undeclared repository. tests/forgejo-repo-reconcile/run-all.sh is what
catches that. The organization setting stays defence in depth and is marked
as not live-proven: POST /orgs is refused for want of write:organization
before the setting is consulted.

Claude-Session: https://claude.ai/code/session_01XdBRc9CHgFhAxCyHbdVtt6
binjovi-bot deleted branch fix/document-the-reachable-creation-path 2026-09-12 13:33:08 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
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
sean/seanfarm!672
No description provided.