From 965fb5a284a1156dd7bd1dd4462af8c6e794c65b Mon Sep 17 00:00:00 2001 From: lyuksovannyy Date: Sun, 13 Sep 2026 00:10:07 +0200 Subject: [PATCH] Read the release token from RELEASE_TOKEN The publish step set GITEA_TOKEN from the org-level API_TOKEN secret, but the Actions runner injects its own GITEA_TOKEN into every step and that value overrides a step-level env: entry of the same name. The org secret was silently ignored and the job failed. Pass the secret as RELEASE_TOKEN instead, which the runner does not reserve, and read that name in release.py. --token-env makes the source configurable, so the runner-provided GITEA_TOKEN can still be used deliberately by passing --token-env GITEA_TOKEN. --- .gitea/workflows/release.yml | 10 +++++----- README.md | 13 +++++++++---- tools/release.py | 16 +++++++++++++--- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 056ac0c..92ab75b 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -69,13 +69,13 @@ jobs: fi echo "tag=${tag}" >> "$GITHUB_OUTPUT" echo "publishing as ${tag}" - - name: Publish release and upload plugin env: - # Org-level secret holding a user token with write:repository scope. - # Not using a `||` fallback to GITEA_TOKEN: Gitea's expression engine - # supports only always(), so the operator would not evaluate. - GITEA_TOKEN: ${{ secrets.API_TOKEN }} + # Deliberately not named GITEA_TOKEN: the Actions runner injects its + # own GITEA_TOKEN into every step, and that value wins over a + # step-level `env:` of the same name, so the org secret would be + # silently ignored. + RELEASE_TOKEN: ${{ secrets.API_TOKEN }} run: | python3 tools/release.py \ --api "${{ github.server_url }}/api/v1/repos/${{ github.repository }}" \ diff --git a/README.md b/README.md index aee6dda..cf2006c 100644 --- a/README.md +++ b/README.md @@ -123,10 +123,15 @@ token with the `write:repository` scope (which covers releases). Set it once und repo-level secret of the same name takes precedence if one repo ever needs different credentials. -If you would rather not manage a token, Gitea also injects a per-job `secrets.GITEA_TOKEN` -that can create releases in its own repo — swap the `env:` line in the workflow and drop the -org secret. It is scoped to the single job and expires with it, so it is the safer default; -`API_TOKEN` is what you want when publishing needs to reach beyond this repo. +The workflow passes it to `release.py` as `RELEASE_TOKEN`, **not** `GITEA_TOKEN`. The Actions +runner injects its own `GITEA_TOKEN` into every step, and that value overrides a step-level +`env:` entry of the same name, so the org secret would be silently ignored and the publish +step would fail on permissions. Use `--token-env` if you need a different variable name. + +If you would rather not manage a token at all, that injected `GITEA_TOKEN` can create +releases in its own repo: pass `--token-env GITEA_TOKEN` and drop the org secret. It is +scoped to the single job and expires with it, so it is the safer default; `API_TOKEN` is what +you want when publishing needs to reach beyond this repo. ## Writing your own plugin diff --git a/tools/release.py b/tools/release.py index 3ea3278..c8f8365 100755 --- a/tools/release.py +++ b/tools/release.py @@ -10,7 +10,9 @@ Stdlib only, so it runs on any runner image without a pip install step. Usage: tools/release.py --api --tag v1.0.0 --artifact dist/x.plugin -The token is read from GITEA_TOKEN (or GITHUB_TOKEN as a fallback). +The token is read from RELEASE_TOKEN by default; override with --token-env. +Deliberately not GITEA_TOKEN: the Actions runner injects its own value for +that name into every step, which overrides a step-level `env:` entry. """ from __future__ import annotations @@ -147,11 +149,19 @@ def main(argv: Optional[list] = None) -> int: parser.add_argument("--artifact", required=True, help="file to attach") parser.add_argument("--commitish", default="", help="target commit for a new tag") parser.add_argument("--asset-name", default="", help="attachment name; defaults to the file name") + parser.add_argument( + "--token-env", + default="RELEASE_TOKEN", + help="environment variable holding the API token (default: RELEASE_TOKEN)", + ) args = parser.parse_args(argv) - token = os.environ.get("GITEA_TOKEN") or os.environ.get("GITHUB_TOKEN") or "" + # Read from an explicitly named variable rather than GITEA_TOKEN: the + # Actions runner injects its own GITEA_TOKEN into every step, which + # silently wins over a step-level `env:` of the same name. + token = os.environ.get(args.token_env, "") if not token: - fail("GITEA_TOKEN is not set") + fail(f"{args.token_env} is not set") artifact = Path(args.artifact) if not artifact.is_file() or artifact.stat().st_size == 0: