From 0c868f7bac7e35f242aba66e8410ac2ad92eda36 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 25 Oct 2026 17:18:22 -0400 Subject: [PATCH] fix(worker): start with a repository-only token The start-up check read the token's account with GET /user, which needs the read:user scope. A token with only repository scope, which is all the worker needs, got 403 and the worker exited in a restart loop. On 403 it now names the commit author from BOT_NAME (default gbr-bot) and still checks push access on the games repo. Co-Authored-By: Claude Opus 5.5 --- BUILDER.md | 1 + cmd/gbrgames/worker.go | 7 ++++++- cmd/gbrgames/worker_test.go | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/BUILDER.md b/BUILDER.md index d6ac700..f53a866 100644 --- a/BUILDER.md +++ b/BUILDER.md @@ -46,6 +46,7 @@ All through environment variables; nothing about where it runs lives in this rep | `OLLAMA_URL` | Ollama for the model (or `MODEL_PROVIDER=gemini` with `GEMINI_API_KEY`) | | `SEARXNG_URL`, `READER_URL` | A SearXNG instance (JSON API) and a page-to-text reader (`READER_URL/`) | | `MODEL`, `POLL_INTERVAL`, `WORKER_NAME`, `WORK_DIR` | Optional: model name, poll interval (20s), name shown in the panel, work folder | +| `BOT_NAME` | Optional: commit author when the token can't read its own account (no `read:user` scope); default `gbr-bot` | ## Running a build by hand diff --git a/cmd/gbrgames/worker.go b/cmd/gbrgames/worker.go index d98c920..c8a62fd 100644 --- a/cmd/gbrgames/worker.go +++ b/cmd/gbrgames/worker.go @@ -60,7 +60,12 @@ func (c *workerConfig) checkBot() error { Email string `json:"email"` } if err := c.giteaGet("/user", &user); err != nil { - return fmt.Errorf("GITEA_TOKEN: %w", err) + if !strings.HasPrefix(err.Error(), "HTTP 403") { + return fmt.Errorf("GITEA_TOKEN: %w", err) + } + // A repository-only token (all the worker needs) may not read its own account: name the author from + // BOT_NAME instead. Whether it can push is still checked below. + user.Login = envOr("BOT_NAME", "gbr-bot") } var repo struct { Permissions struct { diff --git a/cmd/gbrgames/worker_test.go b/cmd/gbrgames/worker_test.go index c1259c5..895d5fe 100644 --- a/cmd/gbrgames/worker_test.go +++ b/cmd/gbrgames/worker_test.go @@ -45,3 +45,26 @@ func TestCheckBotNeedsPushAccess(t *testing.T) { t.Errorf("bad token: got %v", err) } } + +func TestCheckBotWithRepositoryOnlyToken(t *testing.T) { + // Gitea answers /user with 403 when the token only has repository scope + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/user": + w.WriteHeader(http.StatusForbidden) + w.Write([]byte(`{"message":"token does not have at least one of required scope(s), required=[read:user]"}`)) + case "/api/v1/repos/org/games": + w.Write([]byte(`{"permissions":{"push":true}}`)) + } + })) + defer srv.Close() + + t.Setenv("BOT_NAME", "gbr-bot") + c := &workerConfig{gitea: srv.URL, repo: "org/games", botToken: "secret"} + if err := c.checkBot(); err != nil { + t.Fatal(err) + } + if c.botLogin != "gbr-bot" || c.botEmail != "gbr-bot@users.noreply" { + t.Errorf("identity = %s <%s>", c.botLogin, c.botEmail) + } +} -- 2.30.2