You wrote a beautiful set of dropdowns for your pipeline. You opened Run new pipeline. The form was empty. What happened?
The Setup
You have a parent .gitlab-ci.yml that delegates to a child via trigger:include::
|
|
And the child file declares a rich variable schema with options and descriptions:
|
|
You expect ten dropdowns in the Run new pipeline form. You get an empty Variables section and a free-form key/value row.
Why It Happens
GitLab’s Run new pipeline form is rendered before any pipeline exists. At form-render time, GitLab parses one thing: the project’s CI config (.gitlab-ci.yml by default, or whatever CI_CONFIG_PATH points to). Top-level include: directives are followed recursively, because they’re part of the same configuration compilation.
trigger:include: is not followed. It’s a runtime construct, not a config-time one.
When you write trigger:include: .gitlab/pipelines/artifact-sync.yml, you’re telling GitLab: “At runtime, when this job runs, create a brand-new downstream pipeline from that file.” The downstream pipeline is a separate object with its own ID, its own variables, its own jobs. From the parent’s perspective, the child path is just a string in the trigger config — not a file to load and merge.
So the parser stops at the trigger boundary. Anything inside the child file — stages, variables, workflow, even nested include: — is parsed only when the trigger job actually fires. By then the form has long since rendered and disappeared.
The Mental Model
| Mechanism | When parsed | Variables visible in form? |
|---|---|---|
Top-level include: |
Config compile (form-render time) | ✅ |
trigger: include: (child pipeline) |
Runtime, after parent job starts | ❌ |
trigger: project: (multi-project) |
Runtime, on a different project | ❌ |
Same logic: each pipeline has its own compilation scope. The form sees only the parent’s compiled config.
The Fix
Lift the variables to the root .gitlab-ci.yml. They render in the form, and they’re inherited into the child pipeline’s scope by default:
|
|
The dropdowns now render. The child pipeline still sees SYNC_GRAFANA because parent → child variable inheritance is on by default for trigger jobs.
Watch Out For Sibling Pollution
If you have multiple trigger jobs in the parent (say, artifact-sync and repo-mirror), the lifted variables now leak into every child pipeline. Often harmless — they’re just unused env vars — but if you care about scope hygiene or have variable name collisions, opt the unrelated trigger out:
|
|
inherit: variables: false blocks all root-level variables for that job. If the job needs a specific one, switch to the allowlist form: inherit: variables: [VAR_A, VAR_B].
A Subtler Corollary
Even at runtime, variables in the parent’s job env aren’t auto-pushed to the child unless the trigger forwards them. Manually-set UI variables forward by default; some scheduled-pipeline cases don’t. Variable propagation across the parent → child boundary is opt-in, by design — same reason as before: they’re separate pipeline objects.
This is also why spec:inputs defined in a child file doesn’t show up in the parent’s Inputs section either. The form sees one config; child inputs are scoped to the child compile.
Takeaway
The Run new pipeline form is rendered from the parent’s config compile. trigger:include: is a runtime instruction, not part of that compile. If you want a variable to appear in the form, define it where the form is parsed — the project’s root CI config. Anything behind a trigger boundary is invisible to the UI by construction.
When the dropdowns are missing, don’t reach for the docs on value/options/description syntax — reach for git grep "trigger:" .gitlab-ci.yml.