DEV Community

Jordan Huang
Jordan Huang

Posted on

Where Is the Job Behind That Green Prompt?

I keep seeing a green prompt offered as pipeline proof.
Someone ran a script on a box they do not administer.
Then they closed the tab and called the merge safe.

Have you accepted that story during a review?
I want a harder question before anyone clicks approve.
Where is the job, and who stored the trace?

This piece is a myth list with a local checker.
It is not an audit, and it is not a runner guide.
You can drop the helper tools and keep the rule.

The claim that keeps sneaking in

Developers repeat a comfortable sentence after a quick demo.
They say it ran, so the pipeline must be fine.
I break that sentence into five smaller myths below.

Each myth fails when you ask for a GitLab receipt.
A receipt is a pipeline record you can open later.
If you cannot open it, you do not have proof yet.

Myth one: the prompt is the job trace

You run the tests, the prompt returns, and you stop.
That idle prompt feels like a finished job log.
It is not, because GitLab stores the trace with the job.

A foreign shell never writes that trace for you.
Would a screenshot survive a compliance question next month?
I would not bet a release on a cropped image.

Open the job log page, or admit you only have a prompt.
GitLab documents job logs as belonging to the job.
Read the job logs page before you script around traces.

The corrected picture

A prompt is evidence that a shell returned, nothing more.
The trace you can defend lives on the job page.

Myth two: leftover files are artifacts

The scratch directory still holds a tarball after the script.
People call that tarball an artifact and move on.
Job artifacts are files the runner uploads back to GitLab.

A path on a free-floating host is not that upload.
Can you download it from the job page tomorrow?
If the answer is no, you do not have an artifact.

I will not guess retention on a server I do not operate.
The artifacts page describes upload, expiry, and download.
Confirm expire_in in your project instead of borrowing a default.

The corrected picture

An artifact is a file GitLab can hand back to you.
A leftover path is a clue, not a downloadable receipt.

Myth three: drafted rules were already evaluated

A model can write a rules block that looks confident.
Confidence is not the same thing as pipeline creation.
GitLab evaluates rules when it creates the pipeline.

Your bash process does not perform that evaluation.
Did the pipeline even exist for that commit SHA?
If the YAML never landed in the CI config path, no.

Most projects use .gitlab-ci.yml at the repository root.
A custom CI config path is also a real project setting.
Check that setting before you blame the file name.

Start at the CI YAML docs and read the rules section.
A chat file is not the file GitLab compiled.
I link the docs so you can re-check evaluation notes.

The corrected picture

Rules fire only when GitLab creates the pipeline.
Until that record exists, the block is still a draft.

Myth four: a scratch export is a CI variable

You export a token, the command passes, and you cheer.
That export lives in that shell and its child processes.
Project CI variables are injected by GitLab into the job.

Protected variables are withheld from unprotected pipelines and runners.
Confirm the current protection rules before you rely on them.
Should you paste a masked variable into a third-party shell?

No, you should not, because that is a leak.
A leak is not a test, even when the script goes green.
Start at the variables page and the predefined names page.

The corrected picture

CI variables show up inside the job environment GitLab built.
A manual export is a different environment with a different risk.

Myth five: a printed job id means a job ran

Models are happy to invent a plausible job number.
They will also invent a tidy job URL around it.
Predefined names like CI_JOB_ID exist inside a real job.

Outside that process they are ordinary strings you typed.
Would you approve a deploy from a made-up URL?
I hope the answer is a fast no.

Match the URL to a job you can open while logged in.
Then compare the trace line to the note in your ticket.
A matching pair is still a human check, not a script miracle.

The corrected picture

A real job id is printed by the job, then stored by GitLab.
Anything else is a story with numbers in it.

A free draft can still be useful

I still like a cheap place to shape a command.
MonkeyCode offers free model access and a free server option.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.

I ask the model for a draft, then I distrust the draft.
I may retry the command shape on the free server option.
That retry is scratch execution, not a GitLab receipt.

Take MonkeyCode out of the story and the myths remain.
The product is a place to practice, not the proof.
If those options change, the receipt rule does not move.

I am not stating model names, quotas, hardware, or duration.
Those facts were not supplied, so I will not invent them.
Check the product yourself if you need current limits.

Three layers, not one blur

I keep three layers separate when I review a claim.
Mixing them is how a green prompt becomes a false pass.
Ask which layer you hold before you type approve.

  • Draft text is untrusted wording, even when it parses.
  • Scratch execution means a command ran on some host.
  • A receipt means GitLab created a pipeline and a job.

Which layer is in your hand when you type LGTM?
If you cannot answer, you are not ready to merge.
Only the third layer closes a CI proof comment.

The checker I want beside the review

I am offering a small classifier you run locally.
It reads a folder of notes and prints one verdict.
It never logs in, and it never calls the network.

Shape checks are not authentication, and I say so in the output.
Treat the script as a proposal until you run it.
I have not published timings, pass rates, or server specs.

Save it as proof-gap.sh and call it with bash.
Make it executable only on a machine you trust.

Files it expects

  • job_url.txt holds one URL shaped like a GitLab job link.
  • trace_note.txt holds a line you personally saw in the log.
  • variable_source.txt is gitlab_job_env or some other source token.
  • rules_source.txt is pipeline_created only after GitLab compiled CI.
  • image_ref.txt is optional, and a digest uses the sha256 form.

The script

#!/usr/bin/env bash
# proof-gap.sh
# Local evidence classifier. No network. Not authentication.
set -euo pipefail

root="${1:-./evidence}"

need() {
  local f="$1"
  [[ -f "${root}/${f}" && -s "${root}/${f}" ]]
}

say() {
  printf '%s\n' "$1"
}

say "evidence_root=${root}"

if ! need job_url.txt; then
  say "verdict=SCRATCH_ONLY"
  say "missing=job_url.txt"
  exit 0
fi

url="$(tr -d '[:space:]' < "${root}/job_url.txt")"
case "${url}" in
  https://*/-/jobs/[0-9]*)
    say "job_url_shape=plausible"
    ;;
  *)
    say "verdict=NOT_A_JOB_URL"
    exit 0
    ;;
esac

if ! need trace_note.txt; then
  say "verdict=LINK_WITHOUT_TRACE"
  exit 0
fi

if ! need variable_source.txt; then
  say "verdict=ENV_UNPROVEN"
  exit 0
fi

src="$(tr -d '[:space:]' < "${root}/variable_source.txt")"
say "variable_source=${src}"
if [[ "${src}" != "gitlab_job_env" ]]; then
  say "verdict=ENV_NOT_CI"
  exit 0
fi

if ! need rules_source.txt; then
  say "verdict=RULES_UNPROVEN"
  exit 0
fi

rules="$(tr -d '[:space:]' < "${root}/rules_source.txt")"
say "rules_source=${rules}"
if [[ "${rules}" != "pipeline_created" ]]; then
  say "verdict=RULES_NOT_EVALUATED"
  exit 0
fi

if need image_ref.txt; then
  ref="$(tr -d '[:space:]' < "${root}/image_ref.txt")"
  case "${ref}" in
    *@sha256:*)
      say "image_pin=digest"
      ;;
    *)
      say "verdict=IMAGE_NOT_A_DIGEST"
      exit 0
      ;;
  esac
fi

say "verdict=RECEIPT_SHAPE_ONLY"
say "note=open_the_job_and_compare_the_trace"
Enter fullscreen mode Exit fullscreen mode

The shape check only looks for a jobs path plus digits.
A plausible shape still is not proof you can log in.
Open the page yourself before you trust the folder.

How the verdicts map to the myths

The table is a legend, not a policy engine.
Read the verdict, then do the human step beside it.
A tidy folder can still be a lie you told yourself.

Verdict Myth it catches Human step that remains
SCRATCH_ONLY Prompt treated as a trace Commit CI config and wait for a job
NOT_A_JOB_URL Invented or sloppy link Paste a real jobs URL
LINK_WITHOUT_TRACE URL with no log note Open the log and save the line
ENV_NOT_CI Scratch export called CI Re-run inside a job or drop the claim
ENV_UNPROVEN No variable source note Record where the values came from
RULES_NOT_EVALUATED Draft rules called done Wait until GitLab creates the pipeline
RULES_UNPROVEN No rules source note Record whether a pipeline was created
IMAGE_NOT_A_DIGEST Loose tag treated as a pin Record a digest or drop the pin claim
RECEIPT_SHAPE_ONLY Folder looks complete Log in and compare the trace

Run it against two fixtures

Create a folder that mimics a cheerful scratch demo.
Point the classifier at it and read the verdict.
You should see ENV_NOT_CI if you follow this fixture.

The example host is a fixture, not a live project.
Do not expect that URL to open in your browser.

mkdir -p evidence/scratch-demo
printf '%s\n' 'https://gitlab.example.com/group/app/-/jobs/1042' > evidence/scratch-demo/job_url.txt
printf '%s\n' 'I opened the log and copied the failing line.' > evidence/scratch-demo/trace_note.txt
printf '%s\n' 'scratch_export' > evidence/scratch-demo/variable_source.txt
printf '%s\n' 'chat_draft' > evidence/scratch-demo/rules_source.txt
bash proof-gap.sh evidence/scratch-demo
Enter fullscreen mode Exit fullscreen mode

The URL shape is plausible, and the trace note exists.
The variable source is still a scratch export.
That is myth four, caught before anyone approves.

Now flip only the variable source and run again.
The next failure should be the rules source.
A green chain of files can still hide an unevaluated rule.

mkdir -p evidence/rules-gap
cp evidence/scratch-demo/job_url.txt evidence/rules-gap/
cp evidence/scratch-demo/trace_note.txt evidence/rules-gap/
printf '%s\n' 'gitlab_job_env' > evidence/rules-gap/variable_source.txt
printf '%s\n' 'chat_draft' > evidence/rules-gap/rules_source.txt
bash proof-gap.sh evidence/rules-gap
Enter fullscreen mode Exit fullscreen mode

Add a tiny assertion so the fixture fails loudly.
This is a test plan, not a recorded lab result.
Run it on your machine before you trust the script.

out="$(bash proof-gap.sh evidence/rules-gap)"
printf '%s\n' "${out}" | grep -q 'verdict=RULES_NOT_EVALUATED'
printf '%s\n' 'fixture_ok'
Enter fullscreen mode Exit fullscreen mode

What a real job would print

Here is an unexecuted job that prints receipt names.
Do not commit it only to decorate a README.
Commit it when you actually want a pipeline record.

# Unexecuted example. GitLab compiles this only after you commit it
# on the CI config path your project actually uses.
receipt:
  stage: test
  script:
    - echo "CI_PIPELINE_ID=${CI_PIPELINE_ID}"
    - echo "CI_JOB_ID=${CI_JOB_ID}"
    - echo "CI_JOB_URL=${CI_JOB_URL}"
    - echo "CI_COMMIT_SHA=${CI_COMMIT_SHA}"
Enter fullscreen mode Exit fullscreen mode

Those names are predefined inside jobs GitLab starts.
They are not magic strings a model may invent.
Confirm the predefined variables page if you encode policy.

After the job exists, save the URL into job_url.txt.
Write gitlab_job_env only if the values came from that job.
Write pipeline_created only if GitLab showed the pipeline.

Limits you should say out loud

The classifier trusts your files, and your files can lie.
A forged URL with a forged note still looks tidy.
You still have to open GitLab and read the trace.

What can vanish or drift

Free model text can omit image, interruptible, or timeout.
A free server can disappear with your scratch files.
I do not know those limits, so plan for loss.

What you must not paste

Do not put secrets, production tokens, or customer data there.
A practice shell is a bad vault and a bad production runner.
If you need a registered runner, use your own runner fleet.

This method does not prove coverage, flaky retries, or review quality.
Those are different problems and they need their own receipts.
It also does not replace CODEOWNERS, approval rules, or signed commits.

Who should not use this approach

Skip this if you need an audit-grade attestation.
Skip it if the next step handles production secrets.
Skip it if a regulator wants a retained, access-controlled log.

Students and reviewers can use the fixture as a teaching drill.
People who already confuse prompts with traces need it more.
People who already have a strict release ledger do not.

Before you type approve

If a free server is already open, run this classifier there first.
Read the verdict out loud before you type approve.
Then open the real job, because the script cannot log in.

Top comments (0)