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:
With the default, the rendered job contains a self-referencing variable:
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 /useragainst the stored token returned200 OKvia bothPRIVATE-TOKENandAuthorization: Bearer. mainis protected and protected variables do reach scheduled pipelines — proven becauserelease-plz:release/:pr(which use the protectedRELEASE_PLZ_TOKEN) succeed in the same schedule pipeline whererenovate-selffails. So the token reaches the job; it arrives mangled, not missing.- The smoking gun: the
release-plzcomponent 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-selflacked 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.ymltoken wiring only. No input-shape change —tokenkeeps its$RENOVATE_TOKENdefault, 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.