The drift detector merged in #11 was inert, and would have stayed inert silently.
infra/gitea/set-ci-token.sh minted a token and PUT it to the secret GITEA_SETTINGS_TOKEN.
Gitea reserves the GITEA_ prefix for secret names — as GitHub reserves GITHUB_ — and
rejects that with:
HTTP 400 {"message":"invalid secret name"}
The script sent the PUT to /dev/null and printed ==> stored. unconditionally, and curl -sS
exits 0 on an HTTP error. So it reported success for a store that never happened. Measured
afterwards: the token was in the user's token list, and the repo's secret list was empty.
ci/gitea-settings.sh would have skipped on every CI run forever — and with REQUIRE_GITEA_SETTINGS=true it would instead have blocked every PR, including the one fixing
it.
Changes
Secret is now SETTINGS_API_TOKEN; the check reads it, keeping GITEA_SETTINGS_TOKEN as a
local-run fallback.
set-ci-token.sh checks the PUT's HTTP status and reads the secret back, refusing to claim
success otherwise. That missing verification is the real defect — the illegal name is only what
it concealed.
gate.yml maps the secret and enables REQUIRE_GITEA_SETTINGS (the change Keith made
locally; it could not be pushed to main directly, which is protection working correctly).
Why enabling it in this PR is safe
This PR's own CI run is the proof. If the stage were still skipping, REQUIRE turns that into
a failure and this PR goes red rather than merging. Green means the token resolved in CI and
live Gitea matched infra/gitea/expected-branch-protection.json.
Verified locally before pushing:
Condition
Result
REQUIRE=true, token present
gitea-settings: green → MERGE GATE: PASS
REQUIRE=true, no token
gitea-settings: SKIPPED → BLOCKED (not a silent skip)
The secret is stored and read back as present, so CI has what it needs.
**The drift detector merged in #11 was inert, and would have stayed inert silently.**
`infra/gitea/set-ci-token.sh` minted a token and PUT it to the secret `GITEA_SETTINGS_TOKEN`.
Gitea **reserves the `GITEA_` prefix** for secret names — as GitHub reserves `GITHUB_` — and
rejects that with:
```
HTTP 400 {"message":"invalid secret name"}
```
The script sent the PUT to `/dev/null` and printed `==> stored.` unconditionally, and `curl -sS`
exits 0 on an HTTP error. So it reported success for a store that never happened. Measured
afterwards: the token was in the user's token list, and **the repo's secret list was empty.**
`ci/gitea-settings.sh` would have skipped on every CI run forever — and with
`REQUIRE_GITEA_SETTINGS=true` it would instead have **blocked every PR, including the one fixing
it.**
### Changes
- Secret is now **`SETTINGS_API_TOKEN`**; the check reads it, keeping `GITEA_SETTINGS_TOKEN` as a
local-run fallback.
- `set-ci-token.sh` checks the PUT's HTTP status **and reads the secret back**, refusing to claim
success otherwise. That missing verification is the real defect — the illegal name is only what
it concealed.
- `gate.yml` maps the secret and **enables `REQUIRE_GITEA_SETTINGS`** (the change Keith made
locally; it could not be pushed to `main` directly, which is protection working correctly).
### Why enabling it in this PR is safe
**This PR's own CI run is the proof.** If the stage were still skipping, `REQUIRE` turns that into
a failure and this PR goes **red** rather than merging. Green means the token resolved in CI and
live Gitea matched `infra/gitea/expected-branch-protection.json`.
Verified locally before pushing:
| Condition | Result |
|---|---|
| `REQUIRE=true`, token present | `gitea-settings: green` → **MERGE GATE: PASS** |
| `REQUIRE=true`, no token | `gitea-settings: SKIPPED` → **BLOCKED** (not a silent skip) |
The secret is stored and read back as present, so CI has what it needs.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The drift detector merged in #11 was inert and would have stayed inert silently.
WHAT HAPPENED. infra/gitea/set-ci-token.sh minted a token and PUT it to the repo Actions
secret GITEA_SETTINGS_TOKEN. Gitea RESERVES the `GITEA_` prefix for secret names -- exactly
as GitHub reserves `GITHUB_` -- and rejects that PUT with:
HTTP 400 {"message":"invalid secret name"}
The script sent the PUT to /dev/null and printed "==> stored." unconditionally, and
`curl -sS` exits 0 on an HTTP error, so it reported success for a store that never happened.
Measured afterwards: the token existed in the user's token list, and the repo's secret list
was EMPTY. ci/gitea-settings.sh would have skipped on every CI run forever, and with
REQUIRE_GITEA_SETTINGS=true it would instead have BLOCKED EVERY PR -- including the one
fixing it.
Three changes:
* the secret is SETTINGS_API_TOKEN; ci/gitea-settings.sh reads it, keeping
GITEA_SETTINGS_TOKEN as a local-run fallback;
* set-ci-token.sh checks the PUT's HTTP status AND reads the secret back, refusing to claim
success otherwise. That missing verification is the actual defect -- the wrong name was
just what it hid;
* gate.yml maps the secret and ENABLES REQUIRE_GITEA_SETTINGS.
WHY ENABLING IT HERE IS SAFE: this PR's own CI run is the proof. If the stage were still
skipping, REQUIRE turns that into a failure and this PR goes RED rather than merging. Green
means the token resolved and live Gitea matched infra/gitea/expected-branch-protection.json.
Verified locally before pushing:
REQUIRE=true, token present -> MERGE GATE: PASS
REQUIRE=true, no token -> gitea-settings SKIPPED -> BLOCKED (not a silent skip)
The secret is already stored and read back as present, so CI has what it needs.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The drift detector merged in #11 was inert, and would have stayed inert silently.
infra/gitea/set-ci-token.shminted a token and PUT it to the secretGITEA_SETTINGS_TOKEN.Gitea reserves the
GITEA_prefix for secret names — as GitHub reservesGITHUB_— andrejects that with:
The script sent the PUT to
/dev/nulland printed==> stored.unconditionally, andcurl -sSexits 0 on an HTTP error. So it reported success for a store that never happened. Measured
afterwards: the token was in the user's token list, and the repo's secret list was empty.
ci/gitea-settings.shwould have skipped on every CI run forever — and withREQUIRE_GITEA_SETTINGS=trueit would instead have blocked every PR, including the one fixingit.
Changes
SETTINGS_API_TOKEN; the check reads it, keepingGITEA_SETTINGS_TOKENas alocal-run fallback.
set-ci-token.shchecks the PUT's HTTP status and reads the secret back, refusing to claimsuccess otherwise. That missing verification is the real defect — the illegal name is only what
it concealed.
gate.ymlmaps the secret and enablesREQUIRE_GITEA_SETTINGS(the change Keith madelocally; it could not be pushed to
maindirectly, which is protection working correctly).Why enabling it in this PR is safe
This PR's own CI run is the proof. If the stage were still skipping,
REQUIREturns that intoa failure and this PR goes red rather than merging. Green means the token resolved in CI and
live Gitea matched
infra/gitea/expected-branch-protection.json.Verified locally before pushing:
REQUIRE=true, token presentgitea-settings: green→ MERGE GATE: PASSREQUIRE=true, no tokengitea-settings: SKIPPED→ BLOCKED (not a silent skip)The secret is stored and read back as present, so CI has what it needs.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
🤖 Generated with Claude Code