Did your free server paint the job green?
People still call that badge a signed contract.
I do not buy that story at all.
A green badge answers only one small question.
Something ran once, on some machine, under rules you may not share.
Which repeated claims still hide inside that badge?
I keep four of them in this FAQ.
Each one gets a claim, a check, and a cleaner model.
None of those claims gets a free pass from me.
What I mean by a receipt
I want three papers before I trust a change.
The chat note only counts as paper one.
The job log only counts as paper two.
The runner contract only counts as paper three.
Paper one can be fluent and still wrong.
Paper two can be green and still narrow.
Paper three holds registration, tags, and protection flags.
Can one free minute really replace all three?
No, one minute cannot replace the other papers.
It can only fill paper two, and only for that run.
Ask yourself which paper you are actually holding.
If you cannot name it, you are still guessing.
Myth 1: A free job shares production tags
The claim
Here is the claim I want to retire first.
The project is the same, so the runner class matches.
That leap quietly skips the runner registration step.
The check
Shared capacity and your group runner still differ.
They can share a YAML file and still diverge.
A script match is not a machine match.
What can you actually read after the run?
Look for CI_RUNNER_ID inside the saved job log.
Look for CI_RUNNER_TAGS in that same job log.
Look for CI_RUNNER_DESCRIPTION when the instance prints it.
Empty tags are not some hidden allow-list.
Missing tags are not your real production tags.
A different id means a different contract today.
The corrected model
So what is the corrected mental model here?
Tags are an allow-list, not a friendly vibe.
Name the tag when a private registry is required.
If the free server lacks that tag, stop.
That green run never walked the private path.
Do not promote the result into a release note.
Myth 2: Protected variables ride every free job
The claim
Here is another claim I want retired next.
The variable lives on the project, so every job gets it.
Storage on the settings page is not runtime exposure.
The check
GitLab has long limited protected variables by ref.
They show up on protected branches and protected tags.
They stay hidden on ordinary feature-branch pipelines.
Confirm that exposure rule on the docs you trust.
The flag CI_COMMIT_REF_PROTECTED is what I read first.
The string true and the string false differ.
Those two strings mark two different security worlds.
A feature-branch job often prints false right there.
That job cannot prove your deploy token works.
A missing value is the expected result, not a puzzle.
Did someone paste the raw value into chat?
Stop before the model even answers that prompt.
The corrected model
A useful receipt never needs the secret itself.
Keep the variable name and the protection flag.
Record whether this job could see the name.
What model should replace that casual claim now?
Storage, exposure, and proof are three separate steps.
Free compute does not promote your feature branch.
Free model access does not widen variable scope.
A chat replay of a secret is not a test.
It is a leak with extra steps attached.
Myth 3: Fluent prose validated your YAML
The claim
A softer claim shows up after a late edit.
The model rewrote the job, so the rules are safe.
Reading text is not the same as scheduling work.
The check
I already file a chat draft as an unread diff.
This myth still sneaks past that careful habit.
Local consistency can still fail on your runner.
An image name can exist in prose and fail.
A service alias can look tidy and stay unreachable.
A needs edge can be valid text and still wait.
What do I ask the model to produce instead?
I ask for an assumption list I can check.
Which image tag did you assume is present?
Which runner tag did you assume is registered?
Which ref protection state did you assume holds?
Which files does this job actually execute today?
The corrected model
Then I check those lines against the job page.
I do not ask the model to bless the pipeline.
I ask it to expose every quiet guess.
What is the cleaner model I keep nearby?
A suggestion is a diff that carries footnotes.
The footnotes are the useful part of the reply.
The confident closing sentence is just optional noise.
If a footnote has no log line, park it.
Come back only when paper two already exists.
Myth 4: One green run freezes tomorrow
The claim
This claim shows up when a demo felt smooth.
It passed on the free server, so tomorrow will match.
Tags float, caches drift, and mirrors move overnight.
The check
The tag alpine:3.20 is a label, not a digest.
A retag can change bytes without touching your YAML.
A cleared cache can turn one job into another.
I pin by digest when the job gates a release.
I treat cache keys as speed, not as correctness.
I store the job URL beside the commit SHA.
The corrected model
Does a free server make pinning less urgent?
It makes image pinning more urgent for me.
You control that host less than a private runner.
What should you remember after the sample passes?
A pass is one sample, not a freeze-frame.
Store the sample id where you can reopen it.
Do not store a vibe about the environment.
Tomorrow's queue can land on another host.
Your YAML did not freeze that host for you.
A receipt script you can review first
I have not executed this script on your runner.
Treat the file as a proposal, then review it.
It prints facts a normal job already holds.
It does not print secret values on purpose.
Why do I use unset instead of a filler word?
The dash form keeps an empty value empty.
The colon form would hide that difference.
Empty tags and unset tags are different facts.
You can rehearse those branches on a laptop.
This stand-in does not contact GitLab at all.
No token belongs in this local practice command.
The tag line should be empty, not the word missing.
Print the file if your grep behaves differently.
Do not fix the script until you can explain it.
A short job can publish that text file.
Use artifact when always if failed jobs still matter.
Never echo variables that hold tokens or keys.
RECEIPT_IMAGE_HINT is only a label you chose.
GitLab will not invent an image digest for you.
Resolve the digest in a step you already trust.
Then write that digest into the receipt yourself.
#!/bin/sh
# Proposal only. Review before you add it.
set -eu
out="${1:-ci-receipt.txt}"
{
echo "job_id=${CI_JOB_ID-unset}"
echo "pipeline_id=${CI_PIPELINE_ID-unset}"
echo "job_url=${CI_JOB_URL-unset}"
echo "sha=${CI_COMMIT_SHA-unset}"
echo "ref=${CI_COMMIT_REF_NAME-unset}"
echo "ref_protected=${CI_COMMIT_REF_PROTECTED-unset}"
echo "runner_id=${CI_RUNNER_ID-unset}"
echo "runner_desc=${CI_RUNNER_DESCRIPTION-unset}"
echo "runner_tags=${CI_RUNNER_TAGS-unset}"
echo "image_hint=${RECEIPT_IMAGE_HINT-unset}"
} > "$out"
# Local stand-in. This does not contact GitLab.
export CI_JOB_ID=1001
export CI_PIPELINE_ID=55
export CI_JOB_URL=https://gitlab.example/jobs/1001
export CI_COMMIT_SHA=abc123def456
export CI_COMMIT_REF_NAME=feature/demo
export CI_COMMIT_REF_PROTECTED=false
export CI_RUNNER_ID=9
export CI_RUNNER_DESCRIPTION=shared
export CI_RUNNER_TAGS=
export RECEIPT_IMAGE_HINT=alpine:3.20
sh ci-receipt.sh /tmp/ci-receipt.txt
grep -q 'ref_protected=false' /tmp/ci-receipt.txt
receipt:
stage: test
image: alpine:3.20
variables:
RECEIPT_IMAGE_HINT: alpine:3.20
script:
- sh ci-receipt.sh ci-receipt.txt
artifacts:
when: always
paths:
- ci-receipt.txt
A digest check can live beside the receipt file.
I have not run this against your registry.
Point it at the image you actually ship.
# Proposal. Point this at the image you ship.
docker buildx imagetools inspect alpine:3.20
The decision table I keep open
I keep a small table beside the log.
Each row splits real proof from wishful reading.
I read each row from left to right.
If the right cell blocks my claim, I stop.
I go and collect the missing paper instead.
Wishing harder does not fill that empty cell.
| Field | What it proves | What it never proves |
|---|---|---|
| CI_JOB_URL | Where to reopen that run | That a later run matches it |
| CI_COMMIT_SHA | Which commit was checked out | That default-branch YAML matches |
| CI_COMMIT_REF_PROTECTED | Whether this ref is protected | That every variable was exposed |
| CI_RUNNER_ID | Which runner accepted the job | That registration survives tomorrow |
| CI_RUNNER_TAGS | Tags reported for this job | That production tags were implied |
| Image hint | The label you claimed to run | The bytes, unless you pinned a digest |
Where a free model and a free server fit
I still want a first pass on the assumption list.
I still want one real job log I can archive.
MonkeyCode offers free model access and a free server option for that narrow drill.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Those two options are availability claims I was given.
I am not naming a model or quoting a quota.
I am not describing hardware, duration, or a benchmark.
I am not claiming the offer stays open forever.
How do I keep that help inside a narrow lane?
I paste redacted logs, never raw secret values.
I ask for assumption lists, never a pipeline blessing.
I run the receipt script on the free server once.
I compare runner id and tags with the contract.
I refuse to treat a chat summary as paper three.
Why is the free server still worth one trial?
It can fail in a log you can archive.
A missing tag shows up as a skip or an error.
A protected ref flag shows up as plain text.
Why is a free model still worth that first pass?
It stays patient with those boring rules diffs.
It will list branches I skip when I am tired.
It cannot see runner registration unless you provide it.
Want a low-stakes place to practice this split?
Run the receipt once on that free server option.
Then keep only the job URL you can reopen.
Limits I will not talk past
This script trusts predefined variables in the job.
A compromised runner can still lie inside a log.
A green file from a hostile host is not notarized.
I am not offering a security certification here.
I am offering a habit that catches common mix-ups.
Your compliance team may still require a private runner.
Image digests need a resolver you already trust.
The hint variable does not compute those digests.
Copying a tag into the file is a label, not a pin.
Local stand-ins use fake ids on purpose.
They prove how the script branches on empty values.
They prove nothing about your current GitLab version.
Check the variable list for the instance you run.
Shared capacity can be queued or simply unavailable.
A failed start is not proof your YAML is invalid.
Read the job page before you rewrite the config.
A queue delay is not a logic bug in your rules.
Separate capacity pain from a real contract miss.
Who should skip this approach
Skip this when the job handles production secrets.
A free server is the wrong rehearsal room for that.
Use a protected runner and a protected ref instead.
Skip this when you need a stable performance number.
One free job is a single sample, not a benchmark.
I will not pretend that sample is a percentile.
Skip this when your policy forbids external runners.
Availability on a page is not permission to use it.
Your security review outranks a convenient job log.
Skip this when a model asks you to paste a token.
No check in this FAQ needs a live secret.
Redact the log, then ask the question again.
What I do on the next red job
I open the job URL before I read the chat.
I copy the runner id, the tags, and the ref flag.
I ignore the model's first paragraph until those exist.
Then I ask one narrow question of the draft.
Which assumption here contradicts these three logged lines?
That question keeps the chat inside its lane.
Does the green badge still matter after all that?
Yes, it still matters as paper two only.
It never gets to sign paper three for me.
Top comments (0)