I keep hearing one claim in GitLab review threads.
A free server cloned the repo, so it counts as a runner.
Is a shell with git really a runner?
I do not think that claim holds up.
A runner is a process GitLab already knows.
It has an id, tags, and a job history.
A side shell has a checkout and a prompt.
Those two objects are not the same thing.
Why this FAQ exists
I am not posting a speed benchmark here.
I am not naming a chip, a quota, or a timer.
I want a corrected mental model you can test.
You run the checks on your own project.
Then you keep the job trace, not my opinion.
Five claims show up when someone demos a side server.
Each claim sounds practical until you ask for ids.
I answer each one with a check, not a vibe.
Confirm the linked docs on the day you try them.
Myth 1: A clone is registration
People say the repo is on the box, so it is a runner.
GitLab lists runners only after you create one.
A git clone never adds a row to that list.
The runner process must register and stay reachable.
The checkout is just files on a disk.
Open Settings, then CI/CD, then the Runners page.
If that host is missing, it is not your runner.
Docker on the box does not close this gap.
An executor binary is not a registration record.
Read the runner guide before you trust a demo.
The current page is GitLab runners.
Use your version's create-runner flow, not an old snippet.
You can also ask the API with your own read token.
Use it only on a project you maintain.
Do not paste that token into a chat log.
# Proposal: read-only. Run on your machine. I did not run this for you.
test -n "${GITLAB_TOKEN}" && test -n "${PROJECT_ID}" && test -n "${GITLAB_URL}"
curl --silent --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
"${GITLAB_URL}/api/v4/projects/${PROJECT_ID}/runners"
command -v gitlab-runner || printf 'gitlab-runner binary absent\n'
An empty array means no project runner is registered.
A free shell hostname appears only after registration.
That missing row is the myth in one check.
Myth 2: YAML tags grab any host
People say YAML tags bind whatever host they SSH into.
Tags match a job to runners you already registered.
An unknown host is not in that match set.
GitLab leaves the job pending when nothing fits.
Job tags select registered runners, not random servers.
A shared shell prompt is not a tag match.
Here is a probe job I would put on a branch.
It is a proposal, not a pipeline result.
I have not executed it for this article.
# Proposal only. Commit it on a throwaway branch if you choose.
runner_identity_probe:
stage: test
tags:
- probe-only
script:
- test -n "${CI_RUNNER_ID}"
- test -n "${CI_JOB_ID}"
- test -n "${CI_JOB_URL}"
- printf 'runner=%s job=%s\n' "${CI_RUNNER_ID}" "${CI_JOB_ID}"
- printf 'trace=%s\n' "${CI_JOB_URL}"
If no runner has the tag, the job stays pending.
A free server can still run those lines by hand.
That manual run never creates a job id.
A pending job in GitLab is the honest signal.
Read the tags reference before you loosen rules.
Untagged runners are a project choice, not a default gift.
Myth 3: Exported names mint a job
People export CI=true and expect a job token.
The job token is injected into a running job.
The job id and runner id arrive the same way.
A hand export can print names without issuing secrets.
Job identity is issued by GitLab for that run.
You cannot type a trace into the environment.
Run the loop on the side shell before you trust it.
Expect those job fields to be absent there.
Do not print secret values into tickets or chat.
# Proposal: side-shell identity probe. Absent means this is not a job.
for name in CI_JOB_ID CI_JOB_URL CI_JOB_TOKEN CI_RUNNER_ID CI_RUNNER_TAGS CI_PIPELINE_ID; do
if printenv "${name}" >/dev/null; then
printf '%s=present\n' "${name}"
else
printf '%s=absent\n' "${name}"
fi
done
A present name without a job URL is not enough.
Read the job token page before you reuse one.
Scope on that page is not your laptop environment.
Myth 4: A maintainer shell sees protected values
People say maintainers may read protected variables anywhere.
Protected variables go to pipelines on protected refs.
GitLab injects them into that job's environment.
Your SSH session is not that protected pipeline.
Your role in the UI is not an injection path.
Protection follows the pipeline, not your other login.
Confirm the current page before you depend on a toggle.
I will not guess extra switches from memory.
Open protect a CI/CD variable on test day.
If that page adds a runner condition, follow the page.
Do not haul protected values out to a scratch host.
A demo that needs them is already on the wrong machine.
Myth 5: The side box can mint the job JWT
People say cloud login on the free server matches the job.
ID tokens are minted for a job by GitLab.
The cloud trusts GitLab's issuer and those claims.
An outside process does not hold the signing key.
Federation trusts the job token, not a lookalike.
If the token variable is missing, you are outside the job.
# Proposal only. GitLab mints this inside a real job. Never echo the token.
deploy_oidc_probe:
stage: test
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://example.invalid
script:
- test -n "${GITLAB_OIDC_TOKEN}"
- printf 'oidc_token=present\n'
Never print that token into logs or chat.
Read ID token authentication before you copy a sample.
A fake JWT from a script will fail issuer checks.
Myth 6: A zero exit is a green pipeline
People say a zero exit means the pipeline is green.
A job trace carries a job URL and a runner id.
It also carries section timing from the runner.
Your shell history has an exit code and a clock.
Those two records do not point at each other.
Local success is a rehearsal, not pipeline status.
Compare fields, not feelings, before you approve.
Names below are the usual predefined set.
Confirm them on the predefined variables page.
| Field | Inside a real job | On a free shell |
|---|---|---|
| CI_JOB_ID | Numeric id from GitLab | Absent unless you fake it |
| CI_JOB_URL | Link that opens the trace | Absent |
| CI_RUNNER_ID | Id of the runner that picked the job | Absent |
| CI_RUNNER_TAGS | Tags of that runner | Absent |
| CI_PIPELINE_ID | Id of that pipeline | Absent |
| Exit code | Result of the job script | Your local process only |
A faked export can fill names from the left column.
It still will not create a job URL that opens.
Open that URL, or you do not have a job trace.
Where a draft shell still helps
I still want a scratch place to edit the probe.
I do not want that draft to pretend it is CI.
MonkeyCode offers free model access and a free server option.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
That pair is enough to edit YAML and run the loop.
It is not enough to wear your runner tag.
Draft there if you want, then prove the runner id.
Stop when the check needs secrets, deploys, or a release tag.
A useful draft still ends at the runner list.
Decision table I keep beside the config
I keep this table beside the pipeline config.
Use it when a demo skips the runner list.
Each row is a claim, a check, and a model.
| Claim I hear | Check I run | Corrected model |
|---|---|---|
| Clone means runner | Project runner list or runners API | Registration creates the runner |
| Tags bind my SSH host | Job pending without a tag match | Tags select registered runners |
| export CI mints a token | printenv on job fields | GitLab issues job identity |
| Maintainer may read protected vars | Ask where injection happened | Protection follows the pipeline |
| Side box signs the cloud JWT | Look for a real id token in the job | The issuer is GitLab, not the shell |
| Zero exit means green pipeline | Open CI_JOB_URL | Status lives in GitLab |
Who should not use this split
Skip this split when the risk sits in the draft.
These cases should stay on a trusted runner.
- Your policy forbids third-party compute for repository files.
- The branch holds production secrets or customer data.
- You need an audit trail for the draft step itself.
- You are about to deploy, tag, or rotate keys.
- You cannot separate a rehearsal log from a job trace.
Keep those secrets inside GitLab's protected variable path.
Do not paste them into a side prompt to save time.
If the draft host is untrusted, do not clone the repo there.
Limits I will not smooth over
This article is a method, not a lab report.
I did not execute the probe on your project.
I did not measure speed, cost, or uptime.
Free model access and the free server can change.
I am not claiming they are permanent or unlimited.
A model can misread rules, needs, or tags.
The runner creation steps differ by GitLab version.
Use the current UI, not an old token snippet.
Docs pages move, so open the links on test day.
A passed probe does not grant registry or deploy rights.
Use GitLab's predefined variables page as the name list.
Cache, images, and service accounts are separate checks.
The model I want in review
Hold three layers apart when someone says CI passed.
Ask which layer wrote the log in front of you.
- The draft shell can edit files and run local checks.
- The registered runner can pick up a tagged job.
- The pipeline record can show the job and runner ids.
Which layer produced the log you are reading?
If the answer is the draft shell, you have a rehearsal.
If the answer is a job URL, you have a trace.
I would keep this checklist in the repo as a proposal.
I would update it when the runner docs change.
I would not let a green prompt skip the runner list.
Top comments (0)