DEV Community

Jordan Huang
Jordan Huang

Posted on

FAQ: A Clean Lint Is Not a Rules Trace

I keep opening red pipelines where the review job never starts. The YAML still looks fine, so I blame the runner first. Then I ask a worse question and get a calm answer.

Did a lint pass, or did rules actually select that job? I paste the file into chat and wait for a rewrite. A parse on a free server then looks clean enough.

Both results feel like proof, and they are not. A clean parse is not a creation-time decision. A creation-time decision is not a runner result.

The mix-up I keep seeing

I treat green tools like courtroom witnesses now. They only saw the file I chose to hand them. They never saw the event that created the pipeline.

Have you merged a skip you still cannot explain? The job log can be empty without being wrong. I was asking shape questions about a decision failure.

Which clock failed, the parse or the create? I write that question before I accept any patch. Three clocks, three proofs, no shared green light.

Myth one: a lint pass means rules fired

A clean lint means the YAML document parsed. It does not mean rules selected this job. Those are different clocks, and people mix them.

GitLab evaluates job rules when creating the pipeline. That moment needs an event, a ref, and variables. Your laptop copy has none of those inputs today.

So the linter can bless a job production will skip. Or it can ignore a job a later push adds. Shape passed, and the decision still went elsewhere.

Corrected model

Lint checks file shape, while creation checks conditions. I do not swap those two clocks anymore. A green lint is a parse receipt, not a trace.

Myth two: the model saw your pipeline

The model saw the text I pasted into chat. It did not see the merged include graph. Confidence is not the same thing as a fetch.

Include files are pulled when GitLab creates the pipeline. Local files, project files, and remote templates combine. Rules then run on that combined document, not the paste.

A chat paste usually holds only one YAML file. Private includes never arrive in that paste window. Template defaults never arrive in that chat either.

Corrected model

I read a model suggestion as a text hypothesis. I do not read it as a trace of merged config. If the include was absent, the answer stays unfinished.

Myth three: a free server closes that gap

I like a free server for isolated file checks. I do not pretend that server is my GitLab project. The difference matters more than the green text.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. I am stating that before any workflow claim. Please read the limits before you copy the loop.

MonkeyCode's free model access helps me draft hypotheses fast. The free server option lets me run a small checker. I keep both in scratch space, away from runners.

I do not treat either result as pipeline proof. Protected variables are missing from that scratch space. Runner tags and private includes are missing too.

A local pass cannot replay a real push diff. No diff means changes rules stay completely untested. That is a hole, not a minor caveat.

Corrected model

Free tools shrink the draft loop on purpose. Your project still owns the creation trace. I want both, and I never swap their jobs.

Myth four: needs is just faster stages

People say needs only makes stages finish faster. That shorthand hides jobs that rules already removed. Speed talk shows up before the skip talk does.

A job with needs waits on listed jobs only. It does not wait on the whole previous stage. If rules drop a parent, the child can disappear.

The YAML can still look valid after that drop. Stages still group the picture in the editor. They do not record why a node vanished.

Corrected model

Draw the DAG only after rules have been applied. Missing parents are evidence, not a style nit. I want that picture before I call anything fixed.

Myth five: workflow rules match job rules

Workflow rules decide whether any pipeline is created. Job rules decide whether a job exists inside it. Same keyword family, but completely different failure modes.

A model often patches one gate and leaves the other. You can block branch pipelines and blame job rules. Or you create a pipeline whose review jobs are gone.

Did GitLab create zero pipelines, or zero jobs? I ask that before I touch the YAML. Those two outcomes need different files and logs.

Corrected model

Two gates mean two logs, every single time. I do not debug the inner gate first anymore. If the outer gate never opened, I stop there.

Myth six: changes is a pure path filter

Rules changes feels like a simple path glob. It is really a comparison against some ref. That comparison exists only for certain pipeline sources.

Branch pipelines and merge request pipelines do compare differently. Scheduled runs may not carry a push diff at all. No diff in hand means you cannot certify a skip.

GitLab documents this caveat on the job rules page. Read the current page before you trust any skip. Old blogs drift, and stale quotes travel fast.

A free model can quote a stale explanation confidently. A free server still has no push event attached. Neither one can certify today's compare for you.

Corrected model

I treat changes filters as event-shaped, not path-shaped. No event in hand means I have no proof. I write that limit next to the glob itself.

Myth seven: manual means the job is waiting

People see when manual and picture a patient button. Rules can still exclude that job before the UI draws. A manual job that rules dropped never waits around.

When never is a different switch from when manual. The first removes the job from the created pipeline. The second keeps the job and waits for a click.

Corrected model

Ask for those two outcomes to stay split apart. Then confirm the created pipeline shows the button. A chat sentence about manual is not that button.

A gap checklist you can actually run

This script is a proposal you should run yourself. I am not claiming timings from a private lab. It never calls GitLab, and it never simulates rules.

It only lists proofs your local file still lacks. Run it on a scratch copy, not on a secret repo. If parse fails, fix the map before any hypothesis.

#!/usr/bin/env bash
# rules-trace-gap.sh
# Local gap report only. Not a pipeline simulation.
set -euo pipefail

file="${1:-.gitlab-ci.yml}"
test -f "$file" || { echo "missing: $file"; exit 2; }

echo "== file =="
echo "$file"

echo "== parse =="
python3 - "$file" <<'PY'
import sys
path = sys.argv[1]
try:
    import yaml
except ImportError:
    sys.exit("pyyaml missing; parse not proven")
with open(path, encoding="utf-8") as fh:
    data = yaml.safe_load(fh)
kind = type(data).__name__
print("parsed_type:", kind)
if isinstance(data, dict):
    print("top_keys:", ",".join(map(str, data)))
else:
    print("top_keys:", "not-a-map")
PY

echo "== create-time tokens (hints, not results) =="
for token in "include:" "workflow:" "rules:" "changes:" "needs:" "when:"; do
  count="$(grep -c -- "$token" "$file" || true)"
  printf '%s %s\n' "$count" "$token"
done

echo "== gaps a local parse cannot close =="
cat <<'EOF'
- no pipeline source: push, merge request, schedule, or api
- no source ref or target ref for a changes compare
- no merged include graph, including private project files
- no protected or masked variable set from the project
- no runner tag inventory and no created-pipeline DAG
EOF
Enter fullscreen mode Exit fullscreen mode

PyYAML will not understand GitLab tags like !reference. A parse success here is not a GitLab schema pass. Use project CI Lint when you need schema, not this file.

Record three facts after the script finishes running. Did the parse succeed, or did PyYAML fail closed? Which create-time tokens showed up at least once?

Which gap lines are still unchecked on the project? A token count is a hint, not a rule result. Do not let a zero count make you feel safe.

Decision table

Use this table when a tool tries to reassure you. Read across the row, and ignore the confident tone. A yes in column two is not a project yes.

Question Free model Free server Real project
Does the YAML map parse? Guess from pasted text Run the gap script CI Lint syntax check
Which includes merge? Only files you pasted Only files on that disk Creation-time fetch
Which rules match? Hypothesis, no event No pipeline payload Pipeline source and ref
Did changes see a diff? No push event No push event Push or MR compare
Will needs keep parents? Sketch on one file No created DAG Created pipeline graph
Are variables present? Treat as absent Treat as absent Project and group settings
Is manual actually waiting? Wording only No UI button Job listed, play enabled

The last column is the only proof I accept. Everything left of it is draft material for me. I still want those drafts, just not as evidence.

The draft loop I trust

Here is the draft loop I trust enough today. It stays useful even if you remove the product names. The point is the split between hypothesis and trace.

  1. Copy the failed job plus any include stubs you have.
  2. Ask the free model for hypotheses, not a full rewrite.
  3. Require each hypothesis to name its missing input.
  4. Run the gap script on a free server scratch directory.
  5. Match pipeline source, ref, and skipped jobs in the project.
  6. Edit YAML on a branch only after that match holds.

I refuse the model step when the prompt holds secrets. A free chat is still a chat outside your project. Masked values do not belong in that window.

This is the prompt shape I actually want used. It keeps the model in hypothesis mode on purpose. A full rewrite hides the trace you still need.

Do not rewrite the file yet.
List hypotheses for why job "review" might be skipped.
For each one, name the pipeline input you lack.
Separate workflow rules from job rules.
Separate a missing needs parent from a failed script.
Separate when never from when manual.
Say "unknown" when the paste has no include body.
Enter fullscreen mode Exit fullscreen mode

What this will not prove

This loop will not close every CI argument for you. I list the misses so the checklist stays honest. If you need one of these, use another method.

  • It will not prove runner tags match your shared pool.
  • It will not prove the image digest production pulled.
  • It will not prove protected variables exist on that branch.
  • It will not prove a remote include stayed reachable later.
  • It will not survive a docs change you never reread.

GitLab documents these behaviors, and details still move. Check the current YAML and rules pages before policy. Trust the product docs over this FAQ if they conflict.

These are the primary pages I keep open while editing. If a URL moves, search the docs instead of guessing. I will not freeze a version number I have not rechecked.

Who should skip this approach

Skip this if you need a compliance attestation today. A gap script is not an audit log for reviewers. Nobody should attach it to a release record.

Skip this if private components cannot be mirrored locally. You would rehearse the wrong file and feel certain. Certainty about the wrong file is the expensive failure.

Skip this if the failure is a runner or a registry pull. Rules theater will waste the whole afternoon from there. Look at the run clock before you touch rules.

Skip this if you cannot open the created pipeline UI. The last table column is the only proof column. Without that page, you are still guessing in prose.

Ask the smaller question first

Next red pipeline, ask which clock failed first. Was it parse, create, or the later run? A patch that answers the wrong clock makes noise.

I still draft hypotheses with the free model access. Then I run the gap script on the free server. The merge waits until the project trace agrees.

If you already have a MonkeyCode login, try this split on a scratch branch. Keep the merge waiting on the project trace alone. One explained skip beats a calm lint every time.

Top comments (0)