Skip to content
CloudOps
Newsletter Sign up
All prompts
AI for GitLab CI/CD Difficulty: Intermediate ClaudeChatGPT

GitLab CI/CD Variables Debugging Prompt

Diagnose why a GitLab CI/CD variable is missing, masked oddly, expanded wrong, or scoped to the wrong environment — protected, masked, file-type, inheritance, environment scope.

Target user
DevOps engineers debugging variable behavior in GitLab pipelines
Difficulty
Intermediate
Tools
Claude, ChatGPT

The prompt

You are a senior DevOps engineer who can read GitLab variable resolution like a debugger. You know the precedence order (project < group < instance ... vs. job/file overrides), the difference between protected/masked/file/expanded, and the environment-scope footgun.

I will provide:
- The symptom: variable missing in job, value wrong, masking broken, protected variable not appearing
- Where the variable is defined: Project CI/CD → Variables, Group → Variables, Instance, `.gitlab-ci.yml`, scheduled pipeline, manual run input
- The variable's scope settings: protected? masked? expand variable references? environment scope?
- The job that's affected: branch, environment, pipeline source, runner tags
- Output of `echo "$VAR"` from the job log (if not masked) — or the symptom (`$VAR is empty`, `unbound variable`, etc.)

Your job:

1. **Walk variable precedence** (highest priority first):
   1. Variables defined in `.gitlab-ci.yml`'s `variables:` block AT THE JOB LEVEL
   2. Variables passed via `--variable` to `trigger:` or in `pipeline trigger token`
   3. Variables defined at pipeline schedule level
   4. Manual pipeline run "Run Pipeline" UI variables
   5. Project-level CI/CD variables
   6. Group-level CI/CD variables (closest ancestor wins)
   7. Instance-level CI/CD variables
   8. Variables defined at `default:` or top-level `variables:` in `.gitlab-ci.yml`
   9. Predefined `$CI_*` variables (always set)
2. **Protected variables**:
   - "Protected" means: only available on protected branches/tags
   - If branch isn't protected, the variable is invisible to the job — silently
   - Common bug: variable shows in admin UI but not in the job because the branch isn't protected
3. **Masked variables**:
   - Replace value with `[MASKED]` in job logs
   - Have constraints: 8+ chars, no newlines, base64-charset by default (relaxed in newer GitLab)
   - Masking the WRONG variable (e.g., a hash users compare against) makes debugging impossible
4. **File variables (`File` type)**:
   - GitLab writes the value to a temp file; the variable contains the PATH to that file
   - `$KUBECONFIG` defined as File works because tools read the file by env var
   - `$API_KEY` defined as File breaks if the consumer expects the value itself
   - Use File for cert/key contents; use regular Variable for tokens/passwords
5. **Variable expansion**:
   - GitLab can expand `$VAR` references in variable VALUES if "Expand variable reference" is checked
   - If a variable contains `$OTHER`, that gets expanded at runtime — sometimes desired, sometimes a footgun
   - Some tools double-expand (`$$VAR`) — be precise
6. **Environment scope**:
   - A variable scoped to environment `production/*` only appears for jobs with matching `environment:` keyword
   - Easy to misconfigure: variable expected on a job without `environment:` → empty
7. **Job-level `variables:`** OVERRIDE project/group settings for that job — common cause of "I set it in the UI but the job has the old value."
8. **`dotenv` artifact propagation**:
   - `artifacts:reports:dotenv` lets a job export variables to downstream jobs
   - Common pattern: `build` job sets `VERSION=1.2.3` in a `.env` file → exported via dotenv → consumed by `deploy`
9. **`include:` and variables**: included files' `variables:` blocks merge into the pipeline-level variables; later includes override earlier
10. **Verify with `printenv` not `echo`**: `echo "$VAR"` works; `echo $VAR` (no quotes) eats whitespace and is misleading
11. **For masked variables that "stopped working"**: the value may have been changed to one that doesn't meet the mask charset/length requirement → masking silently disabled

For each finding, give the EXACT location to fix (Project / Group / `.gitlab-ci.yml` / Instance) and the override behavior.

---

Symptom: [DESCRIBE — missing, wrong value, masking broken, etc.]
Pipeline context: [branch / env / source / runner]
Variable name and where it's defined:
```
[DESCRIBE — name, location, scope settings]
```
Job's `.gitlab-ci.yml` excerpt (especially `variables:`, `environment:`, `rules:`):
```yaml
[PASTE]
```
Job log output (sanitized):
```
[PASTE]
```

Why this prompt works

Variable resolution in GitLab has many layers (instance/group/project/file/job), several attributes (protected/masked/file/expanded), and per-job overrides. “Variable missing” is a category, not a diagnosis. This prompt forces the model to walk the precedence chain and identify the actual loser.

How to use it

  1. Confirm where the variable is defined. Instance, group (which one — direct parent or higher?), project, scheduled, manual run, or in YAML?
  2. Confirm the branch / environment. Protected variables on non-protected branches and scoped variables on non-matching environments are the top two causes.
  3. Check for a job-level override. A variables: block in the job will replace the UI value for that job.
  4. Capture the job log output (sanitized) so you can see what the runner sees.

Useful commands

# Inside a job (debugging)
- env | grep MY_VAR              # see ALL env vars (careful — masks apply but tools may print plaintext earlier)
- echo "MY_VAR is set to: [$MY_VAR]"    # quotes prevent word-splitting weirdness
- printenv MY_VAR || echo "NOT SET"
- echo "Length: ${#MY_VAR}"      # zero means empty

# Check via GitLab API: pipeline variables
curl --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.example.com/api/v4/projects/<id>/pipelines/<pid>/variables" | jq

# Project-level variables (admin or maintainer)
curl --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.example.com/api/v4/projects/<id>/variables" | jq '.[] | {key,variable_type,protected,masked,environment_scope}'

# Group-level variables
curl --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.example.com/api/v4/groups/<id>/variables" | jq

# Instance-level (admin)
curl --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.example.com/api/v4/admin/ci/variables" | jq

Common findings this catches

  • Variable defined as Protected, branch isn’t protected → invisible to job. Fix: mark branch protected OR un-protect the variable (security trade-off).
  • Variable scoped to production, job has environment: prod → mismatch. The scope is a glob; prodproduction/*.
  • Variable masked, value contains a / or space → silently un-masked. Re-create with mask-compliant value.
  • Variable changed via UI but old pipeline ran with old value → variables resolve at pipeline creation time; restart pipeline.
  • variables: block in YAML with MY_VAR: $OLD_VAR + Expand off → literal $OLD_VAR string in jobs. Turn on expansion or rewrite.
  • include: brings in variables: that override your local → check merge order; place your variables: block AFTER includes.
  • Downstream pipeline missing variablestrigger: doesn’t pass variables by default. Add trigger:project: + variables: block under the trigger.
  • dotenv artifact from build job not in deploy job → check needs: includes the build job; dotenv requires explicit needs: to propagate.

Variable type cheatsheet

TypeStored asUse for
Variable (String)Plain valueAPI tokens, hostnames, simple strings
FileValue written to a temp file; var = pathTLS certs, kubeconfigs, SSH keys
Variable expandedValue expands $REFS at use timeComposed values; rarely needed

Best practices

  • UI-managed variables for ALL secrets; never in YAML
  • Mask all secrets that fit the charset constraints
  • Use File-type for multi-line content (certs, kubeconfigs, SSH keys)
  • Document the variable contract for each repo (especially templates)
  • Audit instance/group variables for stale entries periodically
  • Don’t use $CI_JOB_TOKEN for everything — it can do more than you expect

When to escalate

  • Suspected variable leak in logs (mask failed) — rotate the credential immediately; review log retention.
  • Variables managed in two places conflicting (e.g., group + project both set the same name) — consolidate at the right level; document the precedence.
  • Need for dynamic per-environment values that can’t be done via environment scope — consider external secrets (Vault) instead of GitLab variables.

Related prompts

Newsletter

Free: the DevOps AI Incident-Triage Cheat Sheet

Subscribe and we’ll send you the one-page cheat sheet — plus weekly AI prompts, automation ideas, and tool reviews for infrastructure engineers. One email a week. No spam, unsubscribe anytime.

  • AI Incident-Triage Cheat Sheet (PDF)
  • Access to 600+ DevOps AI prompts
  • One practical workflow email per week