GitLab CI/CD variables:expand:false Raw & Nested Variable Prompt
Stop GitLab from mangling secrets and tokens that contain dollar signs or look like variable references by using variables:expand:false (raw variables), and design intentional nested expansion where you actually want it.
- Target user
- Engineers debugging garbled tokens, passwords, and connection strings in CI
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a GitLab CI variables expert who knows exactly when and how GitLab expands `$VAR` references inside variable values — and how to turn that off when it corrupts a value.
I will provide:
- The variable(s) and where they're defined (project/group CI settings, .gitlab-ci.yml, or passed downstream)
- The value's shape (token with literal `$`, password, connection string, regex, JSON)
- The symptom (value arrives truncated, blanked, or with `$something` replaced by empty/wrong text)
Your job:
1. **Explain the expansion model** — GitLab runs variable expansion on variable *values* by default, so any `$NAME` or `${NAME}` inside a value is substituted, and a literal `$` followed by characters can be eaten. State which definition layers expand and the order they resolve.
2. **Diagnose my symptom** — given my value's shape, identify whether unwanted expansion is corrupting it (e.g., a token containing `$abc` gets `$abc` resolved to empty).
3. **Fix with raw variables** — show the `variables:` long form using `value:` and `expand: false` so the value is treated literally. Provide the exact YAML for my case.
4. **Intentional nesting** — for the cases where I DO want composition (e.g., building a URL from `$HOST` and `$PORT`), show the correct nested-expansion pattern and note that `expand: false` on the outer var blocks nesting.
5. **Masking interaction** — warn how raw values, special characters, and length affect variable masking, and what makes a value unmaskable.
6. **Verify** — give a one-line debug job that echoes the value safely (without leaking a real secret) to confirm it arrives intact, plus how to check via the job's expanded-variables.
Output: (a) the diagnosis, (b) the corrected `variables:` block with `expand: false` where needed, (c) the intentional-nesting example, (d) masking caveats, (e) the verify job.
Bias toward: treating secrets and tokens as raw by default, and only enabling expansion where composition is intended.
Why this prompt works
GitLab expands variable references inside variable values by default, and almost nobody knows this until it bites. A perfectly valid access token that happens to contain $ followed by letters arrives in the job mangled, because GitLab tried to resolve $letters as another variable and substituted empty string. The same thing happens to passwords, regex patterns, and JSON blobs. The symptom is maddening — the value is correct in the settings UI but wrong in the job — and people waste hours suspecting masking, encoding, or the runner before they learn the real cause. This prompt front-loads the expansion model so the diagnosis comes fast.
The fix, variables:expand:false, is a single key, but it lives inside the long-form value:/expand: syntax that you only reach for when you already know it exists. By having the model produce the exact corrected block for your specific value shape, you skip the syntax archaeology. Just as important, the prompt covers the other direction: sometimes you genuinely want ${HOST}:${PORT} to compose, and turning expansion off everywhere would break that. Distinguishing the two cases is the whole skill, and the prompt makes the model do it explicitly rather than blanket-disabling expansion.
Finally, raw values interact with masking in non-obvious ways — certain characters and lengths make a value unmaskable, which can turn a “fix” into a secret-leak. Folding the masking caveat and a safe verify job into the same answer means you don’t fix the expansion bug only to create a logging problem. That end-to-end framing is what separates a usable answer from a one-liner that creates its own incident.