FAQ: A Free Shell Pass Is Not Runner Parity
The red job is not the machine A red pipeline does not mean the patch is wrong. It often means the job saw a different world. I still treat a chat box like that world sometimes. Have you pasted a raw job log into a mo
The red job is not the machine
A red pipeline does not mean the patch is wrong.
It often means the job saw a different world.
I still treat a chat box like that world sometimes.
Have you pasted a raw job log into a model lately?
I have, and the reply sounded oddly sure.
That sureness is not the same as runner parity.
What I am actually comparing
The lane I allow
I sometimes draft with a free model and a free server.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Those two options can sketch a fix and try a snippet.
They cannot inherit my runner, my secrets, or my cache.
Why would a scratch box know my protected refs?
The score I refuse
I am not reporting a benchmark or a quota here.
Nobody handed me hardware notes or retention terms.
So this FAQ is a mental model, not a product score.
Myth: the log is the environment
People say the model saw the failure, so it saw the job.
A job log is a filtered transcript, not a machine image.
Masked values and truncated traces hide the real context.
What did the runner expand before the first command?
I cannot recover that expansion from a red excerpt.
I rebuild the job inputs from the YAML, not the prose.
GitLab can mask a variable when the raw value is printed.
Masking is not a promise that every encoding stays hidden.
I still treat the log as tainted until I read it.
Myth: a free shell matches the job image
A clean compile on a free server feels like proof.
It is proof only for that server's tools and image.
My runner may pin another digest, libc, or mirror.
Did the job declare an image, or did the runner guess?
I write the image name down before I trust a pass.
If the names differ, the pass does not transfer.
Shell executors differ from container executors in a hard way.
One runs on the host path layout you already have.
The other starts from the image entrypoint and a fresh disk.
Myth: a token is fine if the chat feels small
People paste job tokens just to reproduce a private clone.
A job token is a credential, not a debugging convenience.
A free server is still a remote disk you do not own.
Would you drop that token onto a shared build agent?
I would not, so I do not paste it into a prompt.
A polite reply cannot unwind a credential that already left.
I redact tokens before any prompt leaves my laptop.
I rotate anything that already slipped into a paste.
Then I rerun the job so GitLab mints a fresh token.
Is that slower than pasting the whole log?
Yes, and that delay is the point of the check.
Myth: chat order is an incident record
The thread feels like a timeline because it is ordered.
GitLab already stores the job, the commit, and the user.
Those objects are what a later reader can retrieve.
Can you export the chat under the same retention rules?
I do not assume that, so the chat is not my proof.
I copy the corrected YAML into the merge request instead.
A screenshot of a green prompt is not a job artifact.
Artifacts are files the job declared and the runner uploaded.
If I need the file later, I put it in artifacts.
Myth: one YAML file is the whole pipeline
I used to paste only the failing job block.
That habit ignores include files and parent pipeline inputs.
Remote includes may need credentials the scratch box lacks.
Does the model know which include ref your pipeline used?
It does not, unless you paste that resolved file too.
Even then, you should diff against the commit SHA.
Workflow rules and job rules can skip the job you meant.
A free server will not evaluate those rules for you.
You chose a command, and the pipeline may choose another.
I check the pipeline source before I blame the script.
Myth: a suggested cache key joins your cache
Models like to invent cache keys from folder names.
Many caches stay on runners that share a key.
A distributed cache is a separate setup you must name.
A free server does not join that runner cache pool.
You would have to configure that cache yourself on purpose.
I do not do that for a quick draft.
Did the suggestion name a prefix, a fallback, and files?
If not, it is a guess, not a runner setting.
I keep cache paths and artifact paths in separate buckets.
A cache miss on the real runner can still be correct.
The job must pass from a cold cache on purpose.
I do not fix a miss by hiding the install step.
The corrected picture
A runner job binds an image, a ref, rules, and secrets.
A free shell is a scratch disk with tools you can list.
I copy surviving ideas back into the job file only.
Does that scratch disk show up in your runner tag list?
It does not, so the pass stays outside the pipeline.
- I compare image names before I trust a remote pass.
- I keep tokens off the free server and out of prompts.
- I let the project runner judge the branch at the end.
Get the trace without a messy paste
I pull the trace with the job trace API when the UI is slow.
Confirm the path in your current GitLab API docs first.
Use a read token you already trust for that project.
# proposal: ids and host come from your project, not from chat
# never echo the token, and do not upload job.log yet
curl --silent --show-error \
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
--output job.log \
"${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/jobs/${JOB_ID}/trace"
Do not print the token while you debug the curl line.
A shell trace can leak it if you run with xtrace.
I run the curl without set -x for that reason.
A filter I can run twice
I do not want a clever prompt for this risk.
I want a boring filter I can run twice.
This script is a local proposal, not a hosted scanner.
#!/usr/bin/env bash
# redact-job-log.sh — local preflight before any paste
set -euo pipefail
if [[ $# -ne 1 ]]; then
printf 'usage: %s job.log\n' "$0" >&2
exit 2
fi
log=$1
if [[ ! -f $log ]]; then
printf 'missing file: %s\n' "$log" >&2
exit 2
fi
# Heuristics only. Split secrets and encoded blobs can slip through.
patterns=(
'CI_JOB_TOKEN'
'gitlab-ci-token'
'PRIVATE-TOKEN'
'glpat-'
'glrt-'
'AKIA[0-9A-Z]{16}'
'BEGIN [A-Z ]*PRIVATE KEY'
'(PASSWORD|TOKEN|SECRET|KEY)[[:space:]]*[:=]'
)
fail=0
for p in "${patterns[@]}"; do
if grep -E -n -e "$p" "$log"; then
printf 'hit: %s\n' "$p" >&2
fail=1
fi
done
if [[ $fail -ne 0 ]]; then
printf 'refuse to paste; rotate any live credential\n' >&2
exit 1
fi
printf 'no listed patterns; still read the log yourself\n'
Run it before you copy anything into a model.
A zero exit means the listed patterns were absent.
It does not mean the log is safe to share.
Token prefixes can change, so treat this list as a start.
Read your current GitLab docs before you trust a prefix.
I update the list when the instance docs change.
bash redact-job-log.sh job.log
echo exit=$?
Lift the job fields before you ask
I also lift a few fields from the CI file first.
The snippet below is unexecuted proposal code only.
Install PyYAML in a local virtualenv if you try it.
# proposal: do not point this at a secret file
import sys
from pathlib import Path
try:
import yaml
except ImportError:
sys.exit('install pyyaml in a local venv first')
doc = yaml.safe_load(Path(sys.argv[1]).read_text())
if not isinstance(doc, dict):
sys.exit('top level is not a mapping')
for name, job in doc.items():
if not isinstance(job, dict) or 'script' not in job:
continue
image = job.get('image', '<unset; runner may choose>')
services = job.get('services') or []
cache = job.get('cache') or {}
if isinstance(cache, dict):
key = cache.get('key', '<none>')
else:
key = '<unknown>'
print('job=' + str(name))
print('image=' + str(image))
print('services=' + str(services))
print('cache_key=' + str(key))
I paste those printed lines, not the whole secret store.
If include files exist, I run the same idea on each.
A model cannot resolve includes it was never shown.
python3 show_job_contract.py .gitlab-ci.yml
The parity table I fill
I fill this table in the merge request description.
A blank cell means I do not claim parity yet.
The free server column is an experiment, not a gate.
| Job input | GitLab job | Free server draft | Transfer? |
|---|---|---|---|
| Image or digest | job YAML or runner config | image on that box | only if names match |
| Executor | docker, shell, or other | usually a remote shell | usually no |
| Protected variables | protected refs only | must stay absent | must not transfer |
| Job token | minted for that job | never paste it | no |
| Services | services list | not started for you | check yourself |
| Cache | runner cache or named store | empty disk unless you configure it | no by default |
| Artifacts | uploaded paths | files you copy back | manual only |
| Rules and SHA | pipeline evaluation | command you typed | check the SHA |
The walk I actually use
- I download the full job log from the pipeline page.
- I run the redaction script before any text leaves.
- I keep the failing command and the declared image.
- I ask the model for a hypothesis, not a merge.
- I retry on the free server only when secrets stay out.
- I port the surviving change into a branch YAML file.
- I push and let the project runner be the judge.
Which step do people skip when they are tired?
They skip the redaction, then they skip the real push.
I still see both skips, and both waste the next morning.
Who should not use this
Hard stops
Skip this flow when the job holds production secrets.
Skip it when the runner is a locked private shell host.
Skip it when policy forbids third-party text processing.
Races and deploys
I also skip it when the failure is a race I cannot replay.
One remote pass will not explain a timing bug.
I need the job trace and a repeatable fixture instead.
Do not use the free server as a deploy target.
Do not store group tokens in its home directory.
Do not let a model edit protected variables through an API.
Limits I will not paper over
Unknown product terms
I was not given quotas, duration, or hardware details.
I will not pretend those free options last forever.
I will not name models I have not verified in this note.
What the filter misses
The filter misses secrets that wrap across two lines.
It misses values GitLab already replaced with mask text.
You still need a human to read the diff before merge.
What a hint cannot close
A green hint can still hide a wrong base image.
A red hint can still be your own bad fixture.
Either way, the project pipeline remains the gate.
If you already have those two free options, keep them as a scratch pad.
I would rather file the YAML than screenshot the chat.
Your runner still has the last word on the branch.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.