Skip to content

The token-input convention

Several components authenticate to GitLab itself — the state backend (tofu-plan, tofu-apply), the jobs-artifacts API (tofu-apply in ref mode), the package registry (tofu-module-publish), the GitLab API for opening MRs and cutting tags (renovate-self, releaser-pleaser, release-plz). Every one of them follows the same rule: the component exposes a string input for the token, defaulting to $CI_JOB_TOKEN, and never references a consumer's variable by name.

The problem this solves

A reusable component can't know what a given consumer calls their credential. If tofu-apply hardcoded $MY_PROJECT_PLAN_TOKEN, it would only work for the one project that happened to name its variable that. The alternative — every consumer copy-pastes and edits the component — defeats the point of having a shared component at all.

The fix is to make the token an input, defaulting to GitLab's predefined $CI_JOB_TOKEN, which every job gets automatically with no configuration:

include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      plan_token: $MY_ARTIFACT_PAT     # consumer's variable, any name

A consumer whose tier and topology let the job token suffice configures nothing. A consumer who needs a scoped personal access token — a different GitLab tier, a cross-project backend, stricter least-privilege — passes it, under whatever name they've already chosen, without inheriting phpboyscout's naming conventions.

Where the default is enough — and where it isn't

$CI_JOB_TOKEN carries real permissions, and they're narrower than they look:

  • State-backend auth (state_token) — CI_JOB_TOKEN carries terraform_state permission for the job's own project by default. Every phpboyscout stack stores its state in its own project, so the default just works.
  • Package-registry publish (tofu-module-publish's token) — CI_JOB_TOKEN carries write access to its own project's package registry. This works even on GitLab Free, so publishing a module needs no personal access token at all.
  • The jobs-artifacts API (tofu-apply's plan_token in ref mode) — this is where the default quietly stops being enough. Job-token auth for that specific endpoint is a Premium/Ultimate-only feature; on Free it returns a 401. This surfaced for real the first time phpboyscout/infra ran a ref-mode apply — the component had assumed otherwise (2026-05-16-tofu-apply-plan-sources-v0.3.md D2), and 2026-05-19-token-inputs-v0.5.md D3 corrected it: a Free-tier consumer must override plan_token with a personal or group access token carrying job-artifact read.

The lesson generalises: document, in the input's description, exactly when the default stops being sufficient and what scope an override needs — don't make a consumer discover a silent 401 the hard way.

The self-referencing collision

One sharp edge, hit by renovate-self: setting a job variable to the same name as the group variable it's meant to read —

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

— renders, for the default input value, the self-referencing job variable RENOVATE_TOKEN: "$RENOVATE_TOKEN". GitLab resolves a job variable that references a group variable of the same name to a broken value, not the group's real value — so Renovate authenticates with junk and GitLab returns 401 throttle_unauthenticated_api, for every consumer on the default token. The fix is to alias the token to a non-colliding runtime variable name (RENOVATE_TOKEN_RUNTIME) and hand it to the tool via explicit shell expansion rather than another same-named variable assignment. release-plz uses the identical _RUNTIME alias pattern for the same reason.

The rule that stays constant

Regardless of which endpoint or which default, three things never change:

  • The component never hardcodes a credential.
  • The component never names a consumer's CI variable — the input, not a convention, is the contract.
  • A token reaches the runner only through a CI/CD variable; the consumer marks a PAT override Masked, keeping it out of job logs.

See also