Skip to content
Latchkey

GitLab CI Variable Expansion Errors - Empty or Wrong Values in Rules

A $VARIABLE resolved to empty or to the wrong value. GitLab expands variables in defined phases, and a value not yet set at rule-evaluation time, or an over-escaped $$, breaks the logic.

What this error means

A rule never matches because the variable it tests is empty, a script prints a literal $VAR instead of its value, or a nested variable reference does not resolve. The config is valid - the value is just not what you expect.

Job rules / script
# Rule never matches because the variable is set in the same job, not at rule time:
deploy:
  variables:
    TARGET: production
  rules:
    - if: '$TARGET == "production"'   # $TARGET evaluated before job vars apply

Diagnose it: which rule matched, and on which runner?

GitLab evaluates rules: top to bottom and the first match wins, including one that sets when: never. A job that does not run, or runs when you did not expect it to, is nearly always matching an earlier rule than the one you are reading.

.gitlab-ci.yml
# validate the definition against the project
curl -s --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/ci/lint" \
  --data-urlencode "content=$(cat .gitlab-ci.yml)"

# what the job actually sees
script:
  - env | grep -E "^CI_(PIPELINE_SOURCE|COMMIT_REF_NAME|RUNNER)" | sort

Common causes

Variable not available at rule-evaluation time

Rules are evaluated when the pipeline is created. A job-level variables: value, or one set later in a script, is not yet defined when its own rules are checked.

Nested expansion not enabled or mis-keyed

Referencing one variable inside another ($$NAME or ${${NAME}}) only works with the supported nested-expansion syntax. A wrong form yields a literal string.

Over-escaping with $$

In .gitlab-ci.yml, $$ is a literal dollar sign. Using $$VAR when you meant $VAR makes GitLab emit a literal $VAR instead of expanding it.

How to fix it

Define rule variables at the right scope

Use predefined or top-level variables in rules, or rules:variables so the value exists when the rule runs.

.gitlab-ci.yml
deploy:
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      variables:
        TARGET: production
  script:
    - echo "deploying to $TARGET"

Escape literals deliberately

Use $$ only when you want a literal dollar sign reaching the shell; use a single $ to expand a CI variable.

.gitlab-ci.yml
script:
  - echo "CI value: $CI_COMMIT_SHA"      # expanded by GitLab
  - echo "shell var: $$HOME"             # literal $HOME, expanded by the shell

How to prevent it

  • Test rule variables against predefined CI variables that exist at creation time.
  • Use rules:variables to set values that conditionally apply.
  • Reserve $$ for intentional literal dollars passed to the shell.

Frequently asked questions

What causes GitLab CI variable expansion errors?
There are 3 common causes: variable not available at rule-evaluation time, nested expansion not enabled or mis-keyed, and over-escaping with $$. Rules are evaluated when the pipeline is created.
How do I fix GitLab CI variable expansion errors?
There are 2 fixes depending on which cause you have: define rule variables at the right scope and escape literals deliberately. Work through them in order, since the first is the most common.
What does GitLab CI variable expansion errors actually mean?
A rule never matches because the variable it tests is empty, a script prints a literal $VAR instead of its value, or a nested variable reference does not resolve.
How do I stop GitLab CI variable expansion errors happening again?
Test rule variables against predefined CI variables that exist at creation time. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card