Skip to content

title: renovate-self — stop the token input self-referencing RENOVATE_TOKEN description: The renovate-self component renders RENOVATE_TOKEN: "$RENOVATE_TOKEN" from its default token input — a job variable that references the same-named group variable, which GitLab resolves to a broken value. Renovate then authenticates with junk and GitLab returns 401, so every consumer using the default fails. Alias the token to a non-colliding name and pass it via --token, mirroring the release-plz component. status: approved date: 2026-06-19 authors: [Matt Cockayne] tags: [spec, cicd, renovate, components]


Spec: renovate-self token self-reference fix (v0.10.7)

  • Repository: gitlab.com/phpboyscout/cicd
  • Component touched: renovate-self
  • Release: patch — fix: → v0.10.7

Problem

The renovate-self component wires its token like this:

inputs:
  token:
    default: "$RENOVATE_TOKEN"
# ...
variables:
  RENOVATE_TOKEN: "$[[ inputs.token ]]"

With the default, the rendered job contains a self-referencing variable:

variables:
  RENOVATE_TOKEN: "$RENOVATE_TOKEN"

The right-hand $RENOVATE_TOKEN is meant to read the consumer's same-named group variable. But a job variable that references a group variable of the same name is a name collision GitLab does not resolve to the group value — it yields a broken/garbage value. Renovate sends that value in the Authorization header, and GitLab rejects it:

GET https://gitlab.com/api/v4/user = statusCode=401
ratelimit-name: throttle_unauthenticated_api
FATAL: Authentication failure

throttle_unauthenticated_api means GitLab treated the request as anonymous — the credential was not recognised. This breaks every consumer that uses the default token input, regardless of how valid their PAT is. It almost certainly never worked.

How it was diagnosed (on phpboyscout/rust-tool-base)

  • The PAT was provably valid: replaying Renovate's exact GET /user against the stored token returned 200 OK via both PRIVATE-TOKEN and Authorization: Bearer.
  • main is protected and protected variables do reach scheduled pipelines — proven because release-plz:release/:pr (which use the protected RELEASE_PLZ_TOKEN) succeed in the same schedule pipeline where renovate-self fails. So the token reaches the job; it arrives mangled, not missing.
  • The smoking gun: the release-plz component does the identical job — inject a group-secret token — but avoids the collision by aliasing to a different name: RELEASE_PLZ_TOKEN_RUNTIME: "$[[ inputs.token ]]", then passing --git-token "${RELEASE_PLZ_TOKEN_RUNTIME}". renovate-self lacked that indirection.

Why the self-test never caught it

tests/renovate-self/.gitlab-ci.yml passes a literal token: "selftest-invalid-token" (no $). A literal never collides with a same-named variable, so the failure-path self-test exercises a 401 for the expected reason (bad token) and never renders the self-referencing form. The bug only manifests on the default input against a real same-named group variable — which no self-test reproduces.

Decision

Alias the token to a non-colliding name and feed Renovate via --token, using shell expansion in script: (always reliable) rather than GitLab variable expansion of a same-block sibling (itself unreliable):

variables:
  RENOVATE_TOKEN_RUNTIME: "$[[ inputs.token ]]"
script:
  - renovate --token="${RENOVATE_TOKEN_RUNTIME}"

RENOVATE_TOKEN_RUNTIME (LHS) differs from RENOVATE_TOKEN (the group var on the RHS of the default), so GitLab expands it cleanly — exactly the release-plz pattern. The RENOVATE_TOKEN job variable is removed from the block entirely. --token takes precedence over any inherited RENOVATE_TOKEN env, so consumers with or without a same-named group var both get the correct token.

Secrets in logs

The token value never reaches the trace: GitLab echoes the script line with ${RENOVATE_TOKEN_RUNTIME} unexpanded, and any real occurrence of the value is masked because it equals the consumer's masked group variable. Renovate masks its own token config field. This matches the release-plz precedent.

Scope & non-goals

  • In scope: templates/renovate-self.yml token wiring only. No input-shape change — token keeps its $RENOVATE_TOKEN default, so no consumer must change anything to benefit.
  • Non-goals: the schedule gate, the preset, image pinning.

Downstream

rust-tool-base already shipped a consumer-side workaround (a differently-named RENOVATE_PAT group var + token: "$RENOVATE_PAT" override, MR !52) which proved the diagnosis — renovate-self ran green end-to-end. Once this lands and v0.10.7 is cut, that consumer bumps its pin and drops the RENOVATE_PAT override (back to the default), and gtb (and any other consumer) is fixed with the pin bump alone.

Testing

The existing failure-path self-test still applies unchanged: with a literal dummy token, renovate --token="selftest-invalid-token" 401s and exits 1, tolerated by allow_failure.exit_codes: [1, 78]. The same-name collision cannot be reproduced inside the cicd project (it has no RENOVATE_TOKEN group var), so end-to-end verification is the rust-tool-base consumer run above.