Did the Agent Compile Your Pipeline, or Just Read It?
Did a free shell clone just become your pipeline proof?
The model read the YAML and called that pipeline valid.
I do not accept that leap without a compile receipt.
I keep hearing four claims after a quick agent pass.
Each claim treats a local read as GitLab's compiler.
I want a guard that separates those two steps.
Why this bites on review day
A merge request can look calm while the graph is wrong.
Includes fail quietly when the ref or the file moved.
Rules then hide the job you thought you fixed.
Have you merged a green chat and met a red pipeline?
That gap can waste a whole review cycle.
The file on the branch was fine as plain text.
The compiled job graph was not fine at all.
Which layer failed is the only useful question?
A calm diff can still hide a broken graph.
What compiled means in this checklist
GitLab does not execute your YAML as a raw script.
It merges includes, applies inputs, and evaluates rules.
Then it builds jobs for one pipeline source.
A text view shows the file you happened to open.
A remote shell shows the tree you happened to clone.
Neither view is the compile that creates jobs.
Four receipts
I ask for four receipts before I trust a draft.
Those receipts are parse, includes, source, and runner identity.
Miss one, and the green chat is still a guess.
Myth 1: a clean parse means a valid pipeline
Someone pastes the config into a chat window.
The model says the indentation looks structurally fine.
Why do we treat that note as CI lint?
A YAML loader checks mapping shape, not job meaning.
It does not understand rules:, needs:, or trigger:.
Your file can parse and still create zero jobs.
Corrected model
Here is the corrected picture I use in review.
Parse is only the first layer of evidence.
It proves the text is YAML, not that jobs exist.
# Proposed local guard. Unexecuted example, not a project result.
python3 - <<'PY'
import sys
from pathlib import Path
try:
import yaml
except ImportError:
sys.exit('install pyyaml before you trust this parse')
path = Path('.gitlab-ci.yml')
if not path.exists():
sys.exit('missing .gitlab-ci.yml')
data = yaml.safe_load(path.read_text())
if not isinstance(data, dict):
sys.exit('top level is not a mapping')
print('parse_ok keys', ','.join(sorted(data)[:12]))
PY
That command answers only one narrow question for you.
Did a parser accept the top-level mapping in this tree?
Ask project CI lint before you call the result a pipeline.
Project CI lint is the compile receipt I accept for job creation.
It is still not a full rules narrative on its own.
I am not re-arguing that separate point in this note.
Myth 2: seeing include paths means they expanded
I open the file and spot several include: entries.
The model repeats those paths in a tidy list.
Did it fetch the targets, or only echo strings?
Includes may be local, project, remote, template, or component.
A component also carries inputs and a version ref.
Reading the key never expands the merged graph.
An include path is a promise, not a fetched file.
Expansion is a separate fetch plus a merge step.
If that fetch fails, your local text is not the pipeline.
Illustration, not a catalog
The next snippet is an illustration, not a live catalog.
Do not fetch these hosts as if they were your project.
Use them only to see three include shapes side by side.
include:
- local: ci/test.yml
- project: acme/ci-templates
file: /templates/go.yml
ref: v2.4.0
- component: example.gitlab.com/acme/go-test@1.2.3
inputs:
go_version: '1.22'
Count those entries before you celebrate a draft.
If the count is not zero, demand the merged YAML.
Do not let a prose summary replace that artifact.
Myth 3: one shell run covers every source
You ran go test on the branch and it passed.
Will the merge request pipeline select the same jobs?
Will a nightly schedule select those same jobs again?
Job rules: can depend on the pipeline source.
A push, a merge request, and a schedule are different events.
The variable CI_PIPELINE_SOURCE is how GitLab names that event.
One green shell is one context, not every context.
Name the source you actually rehearsed in the note.
Lint again for the source you intend to merge.
Decision matrix
I keep this decision matrix on the review comment.
It separates a local rehearsal from a compiled pipeline.
Read each row before you accept a green chat.
| Question | What a local shell can show | What only GitLab compile can show |
|---|---|---|
| Did the YAML parse? | Yes, if you ran a parser here | Re-check in project CI lint |
| Did includes expand? | Only if you fetched each target | Merged YAML for this project |
| Which source was simulated? | The story you typed into rules | The ref and source you lint |
| Will runner tags match? | No, a shell is not registered | The scheduler's runner match |
Myth 4: a free shell means my tags match
This claim sounds practical, and it still fails.
When a free server hands you a shell, commands can run.
Your job may still declare tags and a resource group.
A handed shell is not a registered GitLab runner.
It has no tag set and no protected flag.
The scheduler never consulted that remote shell session.
Script execution and job scheduling are different problems.
The shell can rehearse the script text you wrote.
Only a matching runner can pick up the real job.
go-test:
stage: test
tags: [linux, docker]
resource_group: staging-db
script:
- go test ./...
Can that job start on the free server session?
No, that session is not your runner fleet.
Use it to rehearse commands, not to prove a match.
This is not a toolchain pin argument either.
I am not asking which Go version sat on the shell.
I am asking whether any runner would even take the job.
Where the free options fit
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Draft partner, not a compiler
I use the free model access as a draft partner only.
I ask it to list include kinds and tag tokens.
I do not ask it to declare the pipeline valid.
I use the free server option as a scratch shell.
I clone the branch, parse the file, and print include kinds.
I still mark that printout as local evidence, not a job log.
This is the workflow I recommend, not a reported run.
I am not stating a quota, a machine size, or a time limit.
Those terms live in the current product notes, not here.
If the shell vanishes, run the same census on your laptop.
The checklist should still make sense without that host.
Copy the script into the repo if you want it versioned.
# Proposed include census. Unexecuted example, not a measured run.
python3 - <<'PY'
import sys
from pathlib import Path
try:
import yaml
except ImportError:
sys.exit('install pyyaml first')
data = yaml.safe_load(Path('.gitlab-ci.yml').read_text())
if not isinstance(data, dict):
sys.exit('top level is not a mapping')
includes = data.get('include', [])
if includes is None:
includes = []
if isinstance(includes, dict):
includes = [includes]
if isinstance(includes, str):
includes = [includes]
print('include_count', len(includes))
kinds = ('local', 'project', 'remote', 'template', 'component')
for item in includes:
if isinstance(item, str):
print('include_kind', 'local_shorthand')
elif isinstance(item, dict):
kind = next((k for k in kinds if k in item), 'unknown')
print('include_kind', kind)
else:
print('include_kind', 'unexpected')
jobs = [
k for k, v in data.items()
if isinstance(v, dict) and 'script' in v
]
print('script_jobs_seen', len(jobs))
print('tagged_jobs_seen', sum(1 for k in jobs if data[k].get('tags')))
PY
# Proposed receipt log. Unexecuted example, not a measured run.
set -eu
test -f .gitlab-ci.yml
: "${SOURCE_TO_SIMULATE:=merge_request_event}"
{
printf 'source_to_simulate=%s\n' "$SOURCE_TO_SIMULATE"
printf 'runner_check=compare tags and resource_group with registered runners\n'
printf 'lint_status=pending until project CI lint is attached\n'
} | tee compile-receipts.txt
Pair the census with GitLab's own CI lint for the project.
Use the lint flow documented for your current GitLab version.
I will not freeze a CLI flag that your version may not ship.
Questions the lint must answer
What should that lint answer before you merge?
- Is the merged config valid inside this project?
- Which jobs exist for this ref and this source?
- Which warnings remain after the includes actually expand?
- Did you simulate the merge request, not only the branch?
If you cannot answer those four questions, stop the merge.
A confident paragraph is not a substitute receipt.
Would you ship a binary you never linked?
The note I paste on the merge request
I want the review comment to stay boring and specific.
No pep talk, and no model transcript pasted in.
Just list the receipts that are still missing today.
Checklist text
Pipeline compile checklist
- [ ] YAML parse ran in this repo, not only in chat
- [ ] Include count is recorded and merged YAML is attached
- [ ] CI lint simulated the pipeline source we will use
- [ ] Job tags and resource_group were checked against real runners
- [ ] Free shell output is marked as rehearsal, not a job log
Would I block a merge when this list is empty?
Yes, when the change touches pipeline config or an include.
A dropped include should not wait for a later patch.
What this guard refuses to prove
The census script does not expand remote includes for you.
It does not evaluate rules or variable precedence either.
It does not query your runner fleet or resource groups.
Default PyYAML may reject GitLab tags such as reference.
A parse error there can be the tool, not your pipeline.
That is another reason the project lint still wins.
The census counts jobs that contain a script key only.
Jobs that only use extends or trigger can be missed.
Treat the numbers as a census, not a job inventory.
Protected variables and file variables stay inside GitLab.
A free shell should not receive those values for a demo.
If a draft asks you to paste a token, stop and refuse.
Component catalogs move when you track a floating ref.
Pin a version and re-lint after you bump that pin.
This script does not resolve spec:inputs on its own.
GitLab's compiler remains the authority for job creation.
Read the current CI lint and include docs before you automate a gate.
I am not pinning a click path that the UI may rename.
Pages I expect you to confirm
GitLab documents includes, CI lint, and predefined variables in its docs.
I am linking the public paths I expect you to confirm.
Start with CI lint and the include reference before you automate.
Who should not use this approach
Skip this if the repository has no .gitlab-ci.yml at all.
Skip it when a platform gate already stores merged YAML.
Skip it when you need a signed release attestation instead.
Also skip it for shared runner capacity planning.
A free shell says nothing about minutes or queue time.
That question belongs on a different sheet entirely.
Do not register the free server as a production runner.
Do not store secrets in the chat that drafted the job.
Do not merge because the model sounded sure today.
Close the loop on the next review
Next time a draft says the pipeline looks valid, ask for receipts.
Run the parse and the include census on the free server, or locally.
Then let GitLab's CI lint close the argument for that source.
Want a second pass on the checklist wording before review?
Free model access is enough to start that pass.
Keep the verdict in the lint result, not in the chat log.
Top comments (0)