release.yml fails on tag push: secrets.GITHUB_TOKEN lacks release-write scope #6

Closed
opened 2026-05-10 07:13:45 +03:00 by claude · 1 comment
Collaborator

Symptom

v2026.05.0 tag push on 2026-05-09 triggered release.yml on the macmini Forgejo Runner (task 2871). Run finished with status=failure (run id 3125, html_url https://git.kapteinis.lv/ojars/latvian-apostrofs/actions/runs/19) within seconds. The first release was created manually via API from the operator MacBook so the README PR (#5) and the Homebrew Cask bump (homebrew-pareizrakstiba 0856cbf) could land tonight.

Root cause

release.yml uses ${{ secrets.GITHUB_TOKEN }} for the Create Forgejo release step (introduced in 00fcf79 per PR #4 review feedback — to avoid the manual FORGEJO_RELEASE_TOKEN setup that spell-checker requires). On this Forgejo instance, the auto-provided GITHUB_TOKEN does not carry release-write scope. The curl -fsS -X POST to /api/v1/repos/{}/releases returns 401, set -euo pipefail fails the step, run is marked failure.

Spell-checker (latvian-spell-checker/.forgejo/workflows/release.yml) explicitly uses secrets.FORGEJO_RELEASE_TOKEN for this exact reason.

Fix options

  1. Set FORGEJO_RELEASE_TOKEN on this repo (Settings → Secrets → Add Secret, value from SOPS forgejo.release_token). Restore release.yml to use it. Symmetric with spell-checker. Pro: zero manual work per release. Con: re-introduces the cross-repo-token concern that motivated the GITHUB_TOKEN attempt.
  2. Remove release.yml entirely + document the manual release procedure in docs/RELEASING.md (build PKG locally with VERSION=X bash build-pkg.sh, create release via API + upload asset, sed-bump the cask). Apostrofs releases are rare (the underlying layout last changed in 2013); manual is proportional. Pro: no secret to manage. Con: no CI safety net.
  3. Add the workflow permissions: block explicitly granting contents: write (Forgejo Actions sometimes requires explicit permission declaration for the runtime token to gain write scope, even with GITHUB_TOKEN). Untested — would need to verify against Forgejo runtime token semantics for this version.

My recommendation: option 2 (delete release.yml, ship docs/RELEASING.md). The CI complexity isn't proportional to the once-every-few-years release cadence of a static keyboard layout.

Acceptance

  • Either: tag push successfully creates Forgejo release with PKG asset attached (option 1 or 3), OR
  • release.yml removed and replaced with documented manual procedure (option 2).
## Symptom `v2026.05.0` tag push on 2026-05-09 triggered `release.yml` on the macmini Forgejo Runner (task 2871). Run finished with `status=failure` (run id `3125`, html_url <https://git.kapteinis.lv/ojars/latvian-apostrofs/actions/runs/19>) within seconds. The first release was created **manually via API** from the operator MacBook so the README PR (#5) and the Homebrew Cask bump (homebrew-pareizrakstiba `0856cbf`) could land tonight. ## Root cause `release.yml` uses `${{ secrets.GITHUB_TOKEN }}` for the `Create Forgejo release` step (introduced in `00fcf79` per [PR #4 review feedback](https://git.kapteinis.lv/ojars/latvian-apostrofs/pulls/4) — to avoid the manual `FORGEJO_RELEASE_TOKEN` setup that spell-checker requires). On this Forgejo instance, the auto-provided `GITHUB_TOKEN` does not carry release-write scope. The `curl -fsS -X POST` to `/api/v1/repos/{}/releases` returns 401, `set -euo pipefail` fails the step, run is marked failure. Spell-checker (`latvian-spell-checker/.forgejo/workflows/release.yml`) explicitly uses `secrets.FORGEJO_RELEASE_TOKEN` for this exact reason. ## Fix options 1. **Set `FORGEJO_RELEASE_TOKEN` on this repo** (Settings → Secrets → Add Secret, value from SOPS `forgejo.release_token`). Restore `release.yml` to use it. Symmetric with spell-checker. Pro: zero manual work per release. Con: re-introduces the cross-repo-token concern that motivated the GITHUB_TOKEN attempt. 2. **Remove `release.yml` entirely** + document the manual release procedure in `docs/RELEASING.md` (build PKG locally with `VERSION=X bash build-pkg.sh`, create release via API + upload asset, sed-bump the cask). Apostrofs releases are rare (the underlying layout last changed in 2013); manual is proportional. Pro: no secret to manage. Con: no CI safety net. 3. **Add the workflow `permissions:` block** explicitly granting `contents: write` (Forgejo Actions sometimes requires explicit permission declaration for the runtime token to gain write scope, even with `GITHUB_TOKEN`). Untested — would need to verify against Forgejo runtime token semantics for this version. My recommendation: option 2 (delete `release.yml`, ship `docs/RELEASING.md`). The CI complexity isn't proportional to the once-every-few-years release cadence of a static keyboard layout. ## Acceptance - Either: tag push successfully creates Forgejo release with PKG asset attached (option 1 or 3), OR - `release.yml` removed and replaced with documented manual procedure (option 2).
Author
Collaborator

Closed — fixed end-to-end on 2026-05-10

Two bugs fixed in this session:

(1) Token scope — switched secrets.GITHUB_TOKENsecrets.FORGEJO_RELEASE_TOKEN. The Forgejo-auto-injected GITHUB_TOKEN lacks release-write scope on this Forgejo, hence the HTTP 401. New secret value sourced from existing forgejo.ojars_token PAT in SOPS (full repo + release-write scope, no new credential created).

(2) doesn't heredoc-in-$() gotcha — discovered while diagnosing #6. The release body was built via BODY=$(cat <<EOF ... EOF). Bash $() command-substitution tracks single quotes within the heredoc body, so the apostrophe in Log out and back in if it doesn't appear immediately. was treated as an unmatched single quote. Bash matched it against '.id' further down (jq -r '.id') and reported unexpected EOF while looking for matching ''with no useful line context. Fully reproducible withbash -n` on the extracted run: block.

Fix: write the body to a temp file outside the $() context, read it back via jq --rawfile body "$BODY_FILE" instead of --arg body "$BODY". Apostrophes (and any other shell-quote-special characters) in the body are now safe.

Validation:

Cross-cutting fix: spell-checker had the same token issue + a separate version-substitution bug (Pareizrakstiba/Info.plist hardcoded 2026.01-fork.1 so any tag != fork.1 silently mismatched the .pkg filename). Both fixed in ojars/latvian-spell-checker@b5e070f; tag v2026.01-fork.2 proved the pipeline. Cask auto-bumped (homebrew-pareizrakstiba@9fe9895).

Commits:

  • ojars/latvian-apostrofs@b7e1772 — release.yml heredoc + token fix
  • ojars/latvian-spell-checker@b5e070f — Info.plist $(VERSION) substitution
  • ojars/homebrew-pareizrakstiba@6e28f9d + 9fe9895 — Cask bumps
  • tokens.enc.yamlojars_note updated to record FORGEJO_RELEASE_TOKEN deployment.

Closing as fixed.

## Closed — fixed end-to-end on 2026-05-10 Two bugs fixed in this session: **(1) Token scope** — switched `secrets.GITHUB_TOKEN` → `secrets.FORGEJO_RELEASE_TOKEN`. The Forgejo-auto-injected `GITHUB_TOKEN` lacks release-write scope on this Forgejo, hence the HTTP 401. New secret value sourced from existing `forgejo.ojars_token` PAT in SOPS (full repo + release-write scope, no new credential created). **(2) `doesn't` heredoc-in-$() gotcha** — discovered while diagnosing #6. The release body was built via `BODY=$(cat <<EOF ... EOF)`. Bash `$()` command-substitution tracks single quotes within the heredoc body, so the apostrophe in `Log out and back in if it doesn't appear immediately.` was treated as an unmatched single quote. Bash matched it against `'.id'` further down (`jq -r '.id'`) and reported `unexpected EOF while looking for matching `''` with no useful line context. Fully reproducible with `bash -n` on the extracted run: block. Fix: write the body to a temp file outside the `$()` context, read it back via `jq --rawfile body "$BODY_FILE"` instead of `--arg body "$BODY"`. Apostrophes (and any other shell-quote-special characters) in the body are now safe. **Validation:** - Tagged `v2026.05.1` (real workflow re-trigger, not a no-op) — task 2878 succeeded end-to-end. - Release auto-created at https://git.kapteinis.lv/ojars/latvian-apostrofs/releases/tag/v2026.05.1 with the PKG asset. - Cask bumped to 2026.05.1 (homebrew-pareizrakstiba commit `6e28f9d`). **Cross-cutting fix:** spell-checker had the same token issue + a separate version-substitution bug (`Pareizrakstiba/Info.plist` hardcoded `2026.01-fork.1` so any tag != fork.1 silently mismatched the .pkg filename). Both fixed in `ojars/latvian-spell-checker@b5e070f`; tag `v2026.01-fork.2` proved the pipeline. Cask auto-bumped (`homebrew-pareizrakstiba@9fe9895`). **Commits:** - `ojars/latvian-apostrofs@b7e1772` — release.yml heredoc + token fix - `ojars/latvian-spell-checker@b5e070f` — Info.plist `$(VERSION)` substitution - `ojars/homebrew-pareizrakstiba@6e28f9d` + `9fe9895` — Cask bumps - `tokens.enc.yaml` — `ojars_note` updated to record FORGEJO_RELEASE_TOKEN deployment. Closing as fixed.
Sign in to join this conversation.
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
ojars/latvian-apostrofs#6
No description provided.