Does a green trigger job mean the child passed?
I hear that claim in merge request threads weekly.
People treat the bridge like a normal test job.
What actually ran?
GitLab parent pipelines can start other pipelines.
You start those graphs with the trigger: keyword.
The parent then shows a bridge job instead.
The child runs inside another pipeline graph entirely.
So whose status actually sits on the merge request?
Why this FAQ exists
I keep seeing five claims repeated during rushed review.
They sound reasonable when the parent pipeline is green.
They collapse once you inspect the bridge payload itself.
Each myth below includes a check you can run today.
Myth 1: Green trigger means the child succeeded
Did the child pipeline even finish its jobs?
By default, GitLab can mark the trigger job earlier.
That success often means "downstream pipeline was created."
It does not mean "downstream pipeline already passed."
Read the current GitLab downstream-pipeline documentation first.
Add strategy: depend if the parent must wait.
Newer GitLab also documents strategy: mirror for tracking.
Check your instance before you copy either keyword blindly.
Evidence, not vibes
Look at the parent pipeline in the GitLab UI.
Open the trigger job, not only the child graph.
If it finished in seconds, it probably did not wait.
Then open the downstream pipeline as its own object.
Compare those two finished_at timestamps with your own eyes.
Example parent job
# parent: .gitlab-ci.yml
# Example only. Confirm strategy options on your GitLab version.
stages: [build, delegate]
pack:
stage: build
script:
- echo "built" > dist.txt
artifacts:
paths: [dist.txt]
expire_in: 1 hour
hand_off:
stage: delegate
trigger:
include: child.yml
# strategy: depend # uncomment only after you read your docs
That hand_off job is a bridge.
It is not a script: job on the parent.
It does not run your test suite by itself.
Myth 2: The child YAML in git is what ran
Is your child file even a static committed file?
Dynamic child pipelines use include:artifact on purpose.
The parent job writes YAML, then the child loads it.
Reviewers who only read child.yml can miss that hop.
A drafted child.yml can be unrelated to what executed.
Dynamic include sketch
# Example: generated child config, not a live production file.
emit_child:
stage: build
script:
- python generate_child.py > generated-child.yml
artifacts:
paths: [generated-child.yml]
run_child:
stage: delegate
trigger:
include:
- artifact: generated-child.yml
job: emit_child
strategy: depend
Ask one question before you approve the merge request.
Which job produced the YAML the child executed?
If nobody can answer, you still do not have a receipt.
Primary docs live under GitLab downstream pipelines and trigger:.
Myth 3: The child inherited every parent variable
Did you forward those variables on purpose, though?
Downstream pipelines do not receive a complete parent dump.
GitLab documents trigger:forward as the actual contract.
Those defaults have changed across GitLab versions before.
I do not treat memory as the current default value.
I open the docs for the GitLab version that is running.
# Example forwarding. Verify defaults on your GitLab.
hand_off:
stage: delegate
variables:
DEPLOY_SHA: $CI_COMMIT_SHA
trigger:
include: child.yml
strategy: depend
forward:
yaml_variables: true
pipeline_variables: false
Protected variables may still remain only on the parent.
Masked values should never appear inside any chat log.
The child job log is the only honest witness here.
Myth 4: Parent artifacts are already in the child
Where would those files sit in the child workspace?
A child pipeline is a separate pipeline object.
It does not auto-mount parent artifacts: paths for you.
GitLab has specific patterns for passing artifacts downstream.
needs inside one pipeline is not a bridge across graphs.
You need an explicit download, artifact include, or API fetch.
A check that fails closed
# verify_bridge.py — example auditor, not a GitLab runner.
import json
import sys
def main(path: str) -> int:
data = json.loads(open(path, encoding="utf-8").read())
jobs = data if isinstance(data, list) else data.get("bridges", [data])
failures = []
for job in jobs:
name = job.get("name", "?")
bridge_status = job.get("status")
downstream = job.get("downstream_pipeline") or {}
child_status = downstream.get("status")
child_id = downstream.get("id")
if bridge_status == "success" and child_status not in {"success"}:
failures.append(
f"{name}: bridge={bridge_status} child_id={child_id} child={child_status}"
)
if bridge_status == "success" and not child_id:
failures.append(f"{name}: green bridge, missing downstream id")
if failures:
print("bridge/child mismatch:")
print("\n".join(failures))
return 1
print("bridge statuses match recorded children")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1]))
Save the GitLab bridges payload to disk first.
Then run the auditor against that saved JSON file.
Do not parse a chat summary of the pipeline status.
Myth 5: A free local session proved the hand-off
Can a laptop see your GitLab bridge objects?
I use drafting help. I do not outsource pipeline status.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode offers free model access and a free server option.
That pairing is useful for YAML drafts and this auditor.
It is not a parent pipeline, child pipeline, or bridge job.
The server does not become your GitLab runner fleet.
The model does not hold CI_JOB_TOKEN for your project.
If I paste only the parent YAML, the child graph is missing.
If I paste a green unit test, the trigger strategy is missing.
Ask the session for a checklist, not a merge verdict.
Then run that checklist against GitLab itself.
Artifact: a one-hour verification workflow
This is the method I actually trust in review.
It needs one real pipeline, not a slide deck.
Link the GitLab docs beside the merge request, not folklore.
Useful starting points:
1. Capture the bridge, not the chat
# Example with glab. Replace IDs. Use a token you already own.
PROJECT_ID="123"
PARENT_PIPELINE_ID="987654"
glab api \
"projects/${PROJECT_ID}/pipelines/${PARENT_PIPELINE_ID}/bridges" \
> bridges.json
python verify_bridge.py bridges.json
No glab on the machine? Use curl against the same REST endpoint.
Same JSON shape. Same auditor. Same failure mode.
# Example REST call. Host and IDs are yours to fill.
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_HOST/api/v4/projects/${PROJECT_ID}/pipelines/${PARENT_PIPELINE_ID}/bridges" \
> bridges.json
2. Decision table for the merge request
Print this table in the review, then fill the blanks.
| Parent setup | Green bridge means | Still check |
|---|---|---|
trigger: only |
Child pipeline was created | Child status, manual jobs |
strategy: depend |
Child finished; parent waited | Which child, which ref |
strategy: mirror |
Status should track the child | Your GitLab version |
include:artifact |
Generated YAML was included | The artifact job, not git |
forward: set |
You chose a variable contract | Protected vars, empty values |
A blank cell is a review blocker, not a style nit.
3. Child ref check
Did the child run the commit you think it ran?
Compare CI_COMMIT_SHA in both pipeline headers yourself.
For multi-project triggers, also compare the target project ref.
A green child on main does not bless this merge request.
4. Artifact presence check
# child.yml — example probe job
probe_inputs:
stage: test
script:
- echo "child sha=${CI_COMMIT_SHA}"
- test -f dist.txt && echo "parent artifact unexpectedly present" && exit 1
- echo "no parent dist.txt mounted, as expected"
That probe is mean on purpose during the first review.
It documents the isolation most trigger YAMLs quietly forget.
Remove the failure only after you add a real fetch path.
Corrected mental model
Treat trigger: as a message, not as a test job.
The bridge reports a contract you chose in YAML.
Creation, dependence, and mirroring are different contracts.
The child pipeline is a different object with its own id.
Variables, artifacts, and refs are separate contracts too.
A child pipeline and a multi-project pipeline are not twins.
One stays inside your project. The other crosses project boundaries.
A draft session can write YAML for either shape.
Only GitLab can instantiate the graph and store status.
Limitations
This FAQ does not replace instance-specific GitLab docs.
Self-managed GitLab often lags features. Check yours first.
strategy: mirror is version-sensitive, so read that page.
The auditor trusts the JSON file you actually saved.
Garbage payload in means garbage mismatch out later.
I am not claiming production incident counts or timings.
These are review habits, not a compliance evidence program.
Who should not use this approach
Do not use a chat verdict for protected production deploys.
Do not paste masked variables into any model session.
Do not skip strategy because a draft looked sequential.
If you need signed release evidence, use GitLab's own records.
If you cannot call the bridges API, stop at the UI compare.
A free server is the wrong place for customer secrets.
Teams without permission to read pipeline APIs should not fake the JSON.
What I want reviewers to ask
Which pipeline object is green on this merge request?
When did the bridge finish versus the child pipeline?
Which YAML file did the child actually load at runtime?
Which variables were forwarded on purpose, not by folklore?
Where did parent artifacts enter the child, if they entered at all?
If those answers are missing, the trigger job is not proof.
Draft help is fine. Status theater is not.
Want a second pass on the auditor script and YAML only? A MonkeyCode free session can review those files if tokens and job logs stay off the table.
Top comments (0)