ci: distinguish an auth failure from a missing tag in version_tag

🤖 AI-authored change.

What does this MR do and why?

A TAG_CREATOR_TOKEN that cannot do its job no longer masquerades as a missing tag. glab api exits non-zero on any failure, so a 401 fell through to the else branch of the tag-existence check: the job printed a confident false conclusion, then died on the create with an opaque invalid_token error. Three consecutive weekly releases failed this way — #203.

A project probe after glab auth login catches the expired, revoked and lost-membership cases. It cannot prove write access, so the create carries the same guard and both call one credential_error helper. retry: 2 absorbs a transient gitlab.com 5xx on the probe; the job is re-entrant, so a retry after a successful create takes the "already exists" branch.

The same bug is fixed in gitlab-zoekt-indexer!1047 (merged), which carries the earlier glab api user form — it closes the expired-token case but neither the lost-access nor the write-denied one. Bringing it in line is a follow-up.

How to set up and validate locally

version_tag only runs on the default branch with a VERSION file, so it cannot execute on this MR's pipeline. To exercise it without a token or network:

  1. Save probe.py from the snippet below.
  2. python3 probe.py .gitlab-ci.yml head at this ref. It parses the job's real script steps with yaml.safe_load and runs them under a glab stub whose HTTP outcomes come from a per-arm table.
  3. Expect RESULT head: 15/15 pass. Re-running it against ea19c90 fails exactly the four write-denied assertions.

Transcripts, per-arm counts and the source: $6054372


🤖 Automated change. Mention @johnmason to leave feedback.

🤖 Mechanism, citations and verification (for agents / deep readers)

Mechanism

glab auth login is not a usable signal: with an invalid token it warns and still exits 0 (measured, glab 1.55.0). Both assertions have to be real API calls, so the job has one helper and two guards (.gitlab-ci.yml:283, :292, :305):

credential_error() { ... }                      # one wording, read and write alike
if ! glab api /projects/$CI_PROJECT_ID --silent; then credential_error "read"; exit 1; fi
...
if ! glab api .../repository/tags -X POST ...;  then credential_error "create tags in"; exit 1; fi

retry appears twice in the file — release_create_mr's existing retry: 0 and this new one — and top-level default: sets only tags:, so there is no competing default.

Verification

15/15 at this head. Control ea19c90, the read probe alone, scores 11/15, failing exactly the four write-denied assertions and nothing else — so that arm tests the new guard rather than passing vacuously. Unpatched main scores 7/15. Per-arm counts are in the snippet.

What I did not verify

  • A token downgraded in place on this project. The live 403 that established the read-ok/write-denied shape came from a different project with the same access shape. Before the write guard that case hit the old path, not the probe.
  • No version_tag job has run on a runner. It only runs on the default branch with a VERSION file present, so the first real exercise is the next weekly release after merge. That includes retry: 2, which is runner behaviour: what is checked here is that the key is present and unambiguous and that the step is re-entrant.
  • POST /projects/:id/ci/lint returns 403 for a project Maintainer, so the config was validated by YAML parse plus bash -n per step, not by GitLab's own linter.
  • This MR does not address the other two problems in the parent issue (no Slack notification on failed release pipelines, no proactive token-expiry warning). Those need a CorpSec webhook request and are tracked separately.
Edited by John Mason

Merge request reports

Loading
Loading