DEV Community

Jordan Huang
Jordan Huang

Posted on

FAQ: A YAML Sketch Is Not Your Pipeline Contract

I keep hearing one shortcut in review threads.
Someone asks a model to sketch a pipeline file.
They paste that sketch into the project repo.

Then they treat the open merge as safe.
Is a neat YAML sketch really a pipeline contract?
My short answer to that claim is no.

A contract is what your GitLab project will actually run.
Anything softer is only a story about the file.

The claim I want to retire

The claim sounds practical in a busy review.
People say the YAML looks right, so the pipeline is right.
I get why that shortcut spreads so fast.

A neat file feels like finished technical proof.
A spare host that printed ok feels like more proof.
Neither result is your project's pipeline contract.

Have you shipped a sketch because the chat sounded sure?
I still see that move in code review.

Where a free draft fits

I still want help turning a rough idea into YAML.
MonkeyCode's free model access can draft that sketch.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.

Its free server option can host this checklist.
I do not treat either result as a merge receipt.

What I let the draft do

  • A draft may propose job names and a stage order.
  • A draft may surface a missing key I overlooked.
  • A draft may rewrite a job after lint returns errors.

What I refuse to let it do

  • It does not approve the merge request for you.
  • It does not create protected variables in the project.
  • It does not prove a runner, environment, or release.

Myth 1: Valid-looking YAML is a valid pipeline

The claim

Have you trusted indentation because the file looked clean?
I have watched that confidence fail in review.
YAML can parse and still describe a bad pipeline.

The evidence

A missing rules block changes who runs the job.
A wrong needs list quietly changes the graph.
A copied image tag may not exist on your runners.

GitLab documents job syntax apart from generic YAML.
The current YAML reference lives on the docs site.
Read it before you defend a copied sketch: https://docs.gitlab.com/ci/yaml/

Lint the file against your project, not your eyes.
The project Lint API is the syntax gate.
GitLab documents that endpoint and its parameters here: https://docs.gitlab.com/api/lint/

A tiny counterexample

Here is a tiny sketch people still try to merge.
It is an illustration, not a file from a real repo.
Ask what the checklist would say before you trust it.

test:
  script:
    - echo ok
Enter fullscreen mode Exit fullscreen mode

There is no rules block, so the job trigger is implicit.
There is no artifact contract for the next job.
Lint may still call this valid, and that is the point.

The corrected model

Corrected model: pretty YAML is only a draft.
Project lint is the first real syntax gate.
A later gate is an actual run on your runners.

Myth 2: A spare host covers the project

The claim

Did that script pass on a spare host today?
Good, keep the log, and then slow down.
That host is still not your GitLab project.

The evidence

It does not hold your protected CI variables.
It does not select the runners your tags demand.
It does not apply your environment protection rules.

Protected variables show up only on protected refs.
Read that boundary before you invent a shortcut.
Use GitLab's variables page as the primary source: https://docs.gitlab.com/ci/variables/

Environments are a separate object with their own history.
That history is not a shell session on your bench.
Follow the current environments docs for that boundary: https://docs.gitlab.com/ci/environments/

The corrected model

Corrected model: a free server is a scratch bench.
Your GitLab project remains the real contract boundary.
A bench result never promotes itself into proof.

Myth 3: Suggested variables are already scoped

The claim

Models love to invent tidy variables blocks for you.
They also invent token names that look official.
Have you pasted one of those into CI settings?

The evidence

Please stop before that paste becomes a leak.
A variable name in a sketch is not a value.
It is not a masked value in your settings.

It is not protected just because the chat said so.
Scope lives in project settings and in rules.
A chat line cannot mask, protect, or file a variable.

The corrected model

I keep raw secrets out of the model prompt.
I keep raw secrets out of the job log too.
The checklist compares names only, never live values.

Corrected model: the sketch may only propose names.
You bind real values in GitLab, with intended scope.

Myth 4: A smoke script replaces rules and needs

The claim

A smoke script can prove a command exists locally.
Have you treated that pass as a full pipeline proof?
That leap is the myth I want to retire.

The evidence

It cannot prove rules changes matched this diff.
It cannot prove needs skipped the right later jobs.
It cannot prove an artifact will download next.

Job rules have their own page in the docs.
Follow the current job rules page before you guess: https://docs.gitlab.com/ci/jobs/job_rules/

Artifact report syntax has its own docs page.
Follow that page before you invent a report path: https://docs.gitlab.com/ci/yaml/artifacts_reports/

The corrected model

Corrected model: a smoke test checks the command.
Rules, needs, and artifacts stay in the YAML contract.
Lint plus human review covers that written contract.

Run this contract checklist

What would you accept as evidence in this review?
I want a checklist you can rerun without my word.
This workflow is a proposal, not a lab report.

I am not publishing timings or pass rates here.
Run it yourself, then believe your own output.
Point it at a project your token can read.

Review ritual

  1. Save the sketch as a branch file, not on main.
  2. Run the checklist and archive the lint.json output.
  3. Reject the patch if valid is not true.
  4. Re-read rules, needs, image, and artifacts yourself.
  5. Only then ask a model to explain the remaining diff.

Evidence I accept

  • A local file read only shows the file was readable.
  • A green lint result shows project syntax acceptance.
  • A scratch log shows that host ran that script.
  • A merge still needs human review of the diff.
#!/usr/bin/env bash
# pipeline-contract-check.sh
# Proposal: unexecuted until you run it on a project you can access.
set -euo pipefail

FILE="${1:-.gitlab-ci.yml}"
HOST="${GITLAB_HOST:-https://gitlab.com}"
PID="${GITLAB_PROJECT_ID:?set GITLAB_PROJECT_ID}"
TOKEN="${GITLAB_TOKEN:?set GITLAB_TOKEN}"

test -f "$FILE"

python3 - "$FILE" <<'PY'
import sys
path = sys.argv[1]
text = open(path, encoding="utf-8").read()
if "\t" in text:
    print("tab_indent=seen", file=sys.stderr)
    raise SystemExit(2)
print("file_read=ok")
print("bytes=%d" % len(text.encode("utf-8")))
PY

BODY="$(jq -n --rawfile content "$FILE" '{content: $content}')"
URL="${HOST}/api/v4/projects/${PID}/ci/lint"

HTTP="$(curl --silent --show-error \
  --header "PRIVATE-TOKEN: ${TOKEN}" \
  --header "Content-Type: application/json" \
  --data "$BODY" \
  --output lint.json \
  --write-out "%{http_code}" \
  "$URL")"

echo "lint_http=${HTTP}"
test "$HTTP" = "200"
jq '{valid: .valid, errors: .errors, warnings: .warnings, status: .status}' lint.json

if grep -nE '(AKIA|BEGIN [A-Z ]*PRIVATE KEY|glpat-|ghp_)' "$FILE"; then
  echo "secret_bait=found" >&2
  exit 2
fi
echo "secret_bait=none_seen"
Enter fullscreen mode Exit fullscreen mode
chmod +x pipeline-contract-check.sh
export GITLAB_HOST="https://gitlab.com"
export GITLAB_PROJECT_ID="123456"
export GITLAB_TOKEN="replace-with-a-read-token"
./pipeline-contract-check.sh .gitlab-ci.yml
Enter fullscreen mode Exit fullscreen mode

Run the script from a scratch host if you want isolation.
Do not commit that token, and do not paste it into chat.
Read lint.json before you read the model's opinion.

If valid is false, the sketch is not a contract.
If valid is true, you still have not run the jobs.
Treat a missing field as a docs check, not a victory.

Your GitLab version may shape the lint payload.
Compare the keys you see with the current API page.
Do not treat a null field as a silent pass.

Illustration, not a captured run

This sample only shows the keys I print.
It is not output from a project I measured.
Your errors array may be strings or objects.

{
  "valid": false,
  "errors": ["<message from your GitLab version>"],
  "warnings": [],
  "status": "invalid"
}
Enter fullscreen mode Exit fullscreen mode

A stricter lint pass

GitLab's lint endpoint may offer a simulation option.
Read the current parameter list before you add one.
Use a ref only when you know what it selects.

A simulated create is still not an executed job.
I leave extra lint flags out of the default script.
Add them only after the current API page confirms them.

Decision table

I keep this table beside the merge request.
Ask which row the author has actually cleared.
Do not skip ahead to the model's blessing.

Repeated claim Evidence you actually have Still missing
The YAML looks valid. A human glance Parse, lint, and the job graph
The file was readable. A local file read Project CI Lint
CI Lint says valid. Syntax accepted for that project Runners, variables, and a real run
The spare host printed ok. That host ran that script Project runners and variable scope
The model approved it. A text completion Every gate above

How I split the work

I give the model the current YAML and the lint errors.
I ask for a small patch, not for a blessing.
I apply that patch on a short-lived branch.

I rerun the same checklist against the new file.
Then I read the diff with the lint output open.
The model does not get the last word.

The scratch host holds the script and lint.json.
It should not hold production tokens if you can avoid that.
A read-only token for lint is the widest scope I want.

If policy forbids that, run curl on a trusted runner.
The project remains the source of truth either way.

Need a disposable bench just for this checklist?
Check MonkeyCode's free server docs before you invent a host.
Keep GitLab as the source of truth after that.

Limits you should say out loud

Say these limits out loud before you adopt the flow.
CI Lint does not execute your jobs or deploy anything.
A scratch host does not become your registered runner.

Free model access can change without this page tracking it.
I am not naming models, quotas, hardware, or duration.
Those facts belong on the current product page.

The secret bait grep misses plenty of real secrets.
A tab check is not a full YAML parse.
You need curl, jq, python3, and an API token.

A fork lint result is not the upstream project result.
If GitLab moves a doc path, follow the current page.
I did not execute this script for a published score.

Who should not use this

Skip this flow if you cannot call the Lint API.
Skip it when CI files already hold production secrets.
Skip it when policy allows only a named runner fleet.

Skip it if you will not read errors after a red lint.
A checklist you ignore is just review theater.
This flow is also a poor fit for secret rotation work.

What should stay in your head

A sketch is a proposal from an untrusted author.
Lint is a syntax gate for that specific project.
A scratch host is only a bench for the script.

Your project settings still hold the real contract.
Which gate did you actually clear in this review?
Which gate are you only imagining right now?

Top comments (0)