DEV Community

Jordan Huang
Jordan Huang

Posted on

FAQ: A Green Lint Is Not Your Merge Gate

You can watch a rewritten pipeline look finished after one green lint.
The badge feels final, and the review thread goes quiet.
The default branch can still refuse the jobs you expected.

I keep seeing that sequence in pipeline review threads.
A green lint starts to feel like the real merge gate.
It is only an early check, and the later gates remain.

This FAQ corrects that mix-up with a local evidence report.
I am not shipping a new pipeline product in this piece.
I am correcting a mental model that keeps slipping past review.

Why this keeps happening

A teammate asks a chat model to fix the CI file.
The reply comes back as a full pipeline file rewrite.
The file parses on a laptop, so the review simply stops.

I sometimes use MonkeyCode free model access to draft review questions.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
I also try fixture scripts on the free server option, then stop.

Neither of those steps is the merge gate on your project.
GitLab still has to parse, schedule, and run the jobs.
Treat both offers as draft tools whose current limits you read.

I will not invent quotas, model names, or hardware claims here.
If the current offer page is vague, you do not know the limits.
Read the live offer page on the day you run the draft.

Myth 1: Valid YAML means GitLab accepts the pipeline

What a laptop parse cannot see

YAML can be perfectly valid and still describe a bad pipeline.
A parser mostly checks nesting, keys, and scalar shapes.
GitLab also checks job schema, includes, and project context.

A local load can succeed while the project still rejects the file.
Remote includes are the usual reason that surprise shows up.
Your laptop never fetched those templates, so it never judged them.

Ask yourself one blunt question before you approve the diff.
Did GitLab parse this file, or did only your laptop?
If the honest answer is laptop, you do not have acceptance yet.

Myth 2: A green CI Lint means every job will run

A lint result can say the configuration looks structurally valid.
That statement still does not promise a complete job schedule.
Rules, workflow blocks, and branch names can still hide jobs.

The ref question

A job can be valid and still be skipped on that ref.
A skipped job is not the same thing as a passed job.
Your merge gate may still be waiting on that missing job.

I ask a ref question in review almost every single time.
Which ref did the lint simulate, if it simulated one at all?
If you never named a ref, you did not test branch rules.

Myth 3: A free server run proves the job image

A free server can run a script and fixture you upload.
That run can catch a crash inside your own tiny fixture.
It cannot borrow your project's runner image policy for you.

What a smoke test can show

Your job may set an image, services, and runner tags.
The free server may boot from a completely different base.
A green script there is a smoke test, not a runner receipt.

Did that server mount your cache and the declared services?
Did it use the same entrypoint the job image actually expects?
If you cannot show both, do not claim environment parity.

Myth 4: Chat can see protected variables

A model can describe job tokens in general textbook terms.
It still cannot see your protected or masked variable values.
Pasting those values into a chat is a leak, not a test.

I keep real secrets out of the prompt on purpose.
I use fake fixtures that carry obvious disposable names instead.
Then I compare behavior inside GitLab, not inside the chat transcript.

Would you paste a production token just to prove one job?
I hope your answer stays no, even under a loud deadline.
A free model session does not weaken that secret handling rule.

Myth 5: A pretty diff is a rules simulation

Models are often good at rewriting blocks so they look tidy.
Tidy formatting is not the same thing as evaluated rules.
Change filters, if clauses, and when keys need real inputs.

I do not trust a paragraph that says this job always runs.
I want the expression, the ref, and the changed path list.
Without those three inputs, that confident sentence is only a guess.

A corrected mental model

Hold four separate layers apart when you review generated CI.
Mixing those layers is how the green lint myth survives review.
Name the layer before you accept any safety sentence in the thread.

  1. Draft layer holds chat text and the proposed pipeline diff.
  2. Shape layer holds a local YAML parse and a keyword scan.
  3. Project layer holds CI Lint against that project and named ref.
  4. Behavior layer holds a real pipeline on a throwaway branch.

Free model access belongs in the draft layer and nowhere else.
A free server sits near the shape layer, with fixtures you own.
GitLab owns the project layer and the behavior layer outright.

If a claim skips a layer, I reject that claim in review.
I do not reject the draft itself just for being a draft.
I only refuse to promote it before the missing layer exists.

The artifact: an evidence-gap report

This script does not call GitLab and does not score any model.
It reads a local pipeline file and prints the unproven gaps.
Run it on your own draft before you ask anyone to merge.

#!/usr/bin/env python3
# Local evidence-gap report for a GitLab CI draft.
# This does not lint against your GitLab instance.
# It only shows claims a local file cannot prove.
# Requires PyYAML: python3 -m pip install pyyaml

from pathlib import Path
import sys
import yaml

REQUIRED_GAPS = [
    'include resolution on the project',
    'workflow and rules for the target ref',
    'runner tags and the job image pull path',
    'protected and masked variables',
    'environment, approval, and merge gate settings',
]

def load_ci(path: Path) -> dict:
    data = yaml.safe_load(path.read_text())
    if not isinstance(data, dict):
        raise SystemExit('top level must be a mapping')
    return data

def job_names(data: dict) -> list:
    skip = {
        'stages', 'variables', 'include', 'workflow',
        'default', 'image', 'services', 'spec',
    }
    names = []
    for key, value in data.items():
        if key in skip or str(key).startswith('.'):
            continue
        if isinstance(value, dict) and 'script' in value:
            names.append(key)
    return names

def main() -> None:
    path = Path(sys.argv[1] if len(sys.argv) > 1 else '.gitlab-ci.yml')
    data = load_ci(path)
    jobs = job_names(data)
    print('file:', path)
    print('jobs_with_script:', len(jobs))
    print('local_parse: ok')
    print('proves_merge_gate: no')
    if 'include' in data:
        print('has_include: yes')
        print('include_resolved_here: no')
    else:
        print('has_include: no')
    if 'workflow' in data:
        print('has_workflow: yes')
        print('workflow_simulated_here: no')
    else:
        print('has_workflow: no')
    for name in jobs:
        job = data[name]
        rules = 'yes' if 'rules' in job else 'no'
        image = 'yes' if 'image' in job or 'image' in data else 'no'
        print('job=' + name + ' rules=' + rules + ' image_declared=' + image)
    print('still_unproven:')
    for gap in REQUIRED_GAPS:
        print('- ' + gap)

if __name__ == '__main__':
    main()
Enter fullscreen mode Exit fullscreen mode

Save this fixture as sample-ci.yml if you want a dry local run.
It is a teaching file, not a pipeline I would merge.
The missing include is intentional, so the report cannot fake resolution.

# Fixture only. Not a production pipeline.
stages:
  - test
include:
  - local: templates/hidden.yml
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
test_job:
  stage: test
  image: example.invalid/python:fixture
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - python -m unittest
Enter fullscreen mode Exit fullscreen mode

How to read the output

I label this helper as a local check, not a published lab result.
I have not published pass rates, timings, or accuracy numbers for it.
You should run it on your file and read every gap line.

python3 -m pip install pyyaml
python3 ci_gap_report.py sample-ci.yml
Enter fullscreen mode Exit fullscreen mode

You can see local_parse ok beside proves_merge_gate set to no.
That pairing is the whole point of this small local report.
Do not crop the output before those two lines are visible.

Jobs that hide script under extends may be missing from the count.
Hidden keys that start with a dot are skipped on purpose.
Treat a zero job count as a parser clue, not as an empty pipeline.

Commands that move you up a layer

Confirm every flag against your GitLab version before you trust it.
I am not pinning a CI Lint API schema in this article.
Docs on your own instance beat any remembered snippet from me.

glab ci lint --help
git checkout -b ci-draft-check
git add .gitlab-ci.yml
git commit -m 'test: draft pipeline, not a release'
git push -u origin HEAD
Enter fullscreen mode Exit fullscreen mode
  1. Parse the file locally and save the gap report output.
  2. Read CI Lint help on your instance before you pass any flags.
  3. Push the draft to a throwaway branch and open its pipeline.

Then open the pipeline page for that throwaway branch only.
Compare the visible job names there with the gap report output.
If a job is missing, your rules did not match the written story.

Do not paste protected values into any free model prompt.
Do not point a free server at production data or private networks.
Use tiny fixtures and fake tokens such as EXAMPLE_TOKEN only.

A decision table I use in review

Fill the middle column before you approve

I keep a small decision table inside the merge request comment.
I ask the author to fill the middle evidence column completely.
Empty middle cells mean the old myth is still in force.

Claim in the review Evidence that counts What does not count
YAML is well formed A local parse line from this report A model sentence that says looks good
The project accepted the config CI Lint run against that same project A laptop load that never saw includes
Jobs will run on this ref A pipeline page for that exact ref A chat summary of the rules block
The script survived its image A job log from that declared image A free server boot on another base
Secrets stayed sealed Protected variables, never pasted in chat A prompt that contains the real value

Where draft tools still help

A free model is useful for a first diff and named gaps.
A free server is useful for a shell plus one fixture file.
I use both of those before I spend time on style nits.

I ask the model to list assumptions it has not checked yet.
I do not ask it to declare the pipeline safe or finished.
That wording change keeps this myth from sneaking back into review.

A prompt I keep is deliberately short and a little blunt.
List every assumption this YAML makes about rules, images, and secrets.
Then add one hard line: do not say the pipeline will pass.

Limits you should say out loud

This approach will not evaluate include files you never downloaded.
It will not expand parent-child pipelines or compliance pipeline graphs.
It will not know your runner tags, shared minutes, or license limits.

Free model wording can lag behind your current GitLab keyword set.
Free server time, size, and network policy follow the live offer.
I am not stating those numbers, because I will not guess them.

A throwaway branch can still consume real project CI resources.
It can still notify reviewers when your project settings say so.
Read those notification rules before you push a noisy test branch.

Who should not use this approach

Skip this flow when policy forbids external model tools entirely.
Skip it when the first pipeline push can touch production systems.
Skip it when you need an auditor attestation, not a gap list.

Also skip it when a locked compliance template already generates the YAML.
A local rewrite can violate a company include you must not override.
In that case, ask the template owner instead of a chat window.

  • Do not use a free server as your private runner network.
  • Do not use a free model session as your secret store.
  • Do not treat either tool as approval from a human code owner.

What I want from the next review

Bring the gap report output together with the pipeline diff.
Bring a pipeline link from a non-protected throwaway branch run.
Bring one plain sentence that names what you still did not prove.

If those three items are present, I can review the real risk.
If any of those items is missing, I ask for it before approval.
A green lint by itself will not close the review thread.

If you already use MonkeyCode, keep the free model on drafts only.
Keep the free server on fixture smokes, not on production paths.
Then let GitLab itself close the merge gate, which is its job.

Top comments (0)