Fetching latest headlines…

Dev

FAQ: Which Group Facts Never Ride Along

Dev.toUnited States · NORTH AMERICA

FAQ: Which Group Facts Never Ride Along I stared at a scratch shell that printed my repo name. I treat these drafts as unproven until context shows up. A copied job file is full of familiar names. A...

0 views0 likes0 comments

FAQ: Which Group Facts Never Ride Along

I stared at a scratch shell that printed my repo name.
It felt like the group had followed me in.
Had it, or did I just paste a familiar path?

I treat these drafts as unproven until context shows up.
A common ask is to bless a job nobody has run.
I start with context, not with pretty YAML syntax.

Why the shortcut feels earned

A copied job file is full of familiar names.
A shell prompt looks like work already began.
Those two sights fool tired reviewers every week.

A free model can echo names from the paste.
A free server can give you a shell fast.
Neither sight is a grant from your GitLab group.

I draft this presence check with MonkeyCode free model access.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
I then run it on the free server option and stop.

A scratch host is not my group, and it never was.
Would you merge a job that cannot name its own job id?

Claims I refuse on sight

The shell inherited CI variables

People say the shell already holds the CI variables.
I ask which job injected them, and then I wait.
That silence is the evidence I actually needed.

GitLab injects variables into a specific job process.
A chat window is not that job process.
A spare shell is not that job process either.

Protected variables stay on protected refs and pipelines.
Masked does not mean present in your spare shell.
File-type variables arrive as paths inside the job.

Have you opened the job log and searched for the injection?
No log means you are guessing about scope.
I will not guess about scope in a review comment.

Registry auth came along

A drafted docker push is just a line of text.
Text does not create a Docker config for you.
A model can spell the command and still be logged out.

Did this workspace run a real docker login?
Did a job expose CI_JOB_TOKEN in this process?
If both answers are no, you have no registry grant.

I refuse to treat a suggested push as a login.
Would you ship an image with a fictional auth file?
That mistake often shows up dressed as confidence.

Same cache key, same bytes

Cache is a key, a project, and a runner store.
Your laptop store is not the shared runner store.
A free server store is a third bucket again.

Matching cache key strings can still miss badly.
The bytes live where the runner actually saved them.
Have you blamed a flake when the store was simply elsewhere?

I want the runner id beside the key before I argue.
No runner id means I only have a string.
A bare string is not a cache hit.

The model remembered the environment

Models keep prompt text, not environment protection rules.
A protected environment is a GitLab project setting.
That setting does not live in shell history.

If a draft claims a production deploy, I want the job URL.
No URL means no deploy record I can trust.
A confident sentence is not an environment audit log.

Could the model have copied an old success story from the prompt?
Yes, and that story is still not this run.
I separate memory of text from memory of a deploy.

Agreement means the docs were checked

A free model may agree that your variable scope is fine.
Agreement is not a fetch of the current docs.
Did anyone open the primary docs page in this session?

I keep the links in the review note, not in my head.
GitLab's variable page is the source, not the chat.
If the page and the draft disagree, the page wins.

I do not trust a rule I cannot reopen today.
Read these before you cite a rule, and check the page date:

I am not quoting a version number from memory.
Pages move, and my memory is a bad pin.
The receipt below does not replace those pages.

The artifact: a presence receipt

I want a receipt that lists names and states only.
Values stay out of the file, the log, and the chat.
You can run this on a free server without importing secrets.

Treat the script as an example until your own run exists.
I have not run this against your group.
I will not pretend that a sample run exists.

This example targets Python 3.9 or newer on purpose.
Save the script as context_gap.py beside the pipeline.
Commit only a name list beside that script.

Do not commit values, tokens, or a live env file.
A leaked env file would undo the whole point.
Keep secrets in the job, not in the repo.

#!/usr/bin/env python3
"""Presence receipt. Record names and states, never values."""

import argparse
import json
import os
import socket
from datetime import datetime, timezone
from pathlib import Path


def load_names(path: Path) -> list[str]:
    names: list[str] = []
    for raw in path.read_text(encoding="utf-8").splitlines():
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        if not line.replace("_", "").isalnum() or line[0].isdigit():
            raise SystemExit(f"refusing odd name: {line}")
        names.append(line)
    return names


def state_of(name: str) -> str:
    return "present" if os.environ.get(name) else "absent"


def main() -> None:
    parser = argparse.ArgumentParser(description="CI context gap receipt")
    parser.add_argument("manifest", type=Path)
    parser.add_argument("--out", type=Path, default=Path("context-gap.json"))
    args = parser.parse_args()

    names = load_names(args.manifest)
    docker_cfg = Path.home() / ".docker" / "config.json"
    receipt = {
        "checked_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
        "hostname": socket.gethostname(),
        "ci_job_id": state_of("CI_JOB_ID"),
        "job_token": state_of("CI_JOB_TOKEN"),
        "registry": state_of("CI_REGISTRY"),
        "docker_config": "file_present" if docker_cfg.is_file() else "file_absent",
        "names": [{"name": n, "state": state_of(n)} for n in names],
    }
    args.out.write_text(json.dumps(receipt, indent=2) + "\n", encoding="utf-8")
    print(f"wrote {args.out}")


if __name__ == "__main__":
    main()

Name list

Here is the name list I would commit beside the script.
Names are the only claim I put under test.
Values are never part of the claim file.

# ci-names.txt — names only, never values
CI_PROJECT_ID
CI_PROJECT_PATH
CI_COMMIT_SHA
CI_COMMIT_REF_PROTECTED
CI_REGISTRY
CI_REGISTRY_IMAGE
CI_JOB_ID
CI_JOB_TOKEN
CI_ENVIRONMENT_NAME

Commands

Run the script, then read states, not stories.
Keep the output next to the draft, not inside it.

python3 context_gap.py ci-names.txt --out context-gap.json
python3 -m json.tool context-gap.json

On a scratch shell I expect almost every state to read absent.
That absence is a pass for this myth, not a GitLab outage.
If you export real tokens to flip red into green, you failed.

The JSON below is a shaped example, not a captured log.
Use it to learn the shape, not to cite a measurement.
Your hostname and timestamp will differ, and they should.

{
  "checked_at": "2026-10-12T00:00:00Z",
  "hostname": "scratch-example",
  "ci_job_id": "absent",
  "job_token": "absent",
  "registry": "absent",
  "docker_config": "file_absent",
  "names": [
    {"name": "CI_PROJECT_ID", "state": "absent"},
    {"name": "CI_JOB_TOKEN", "state": "absent"}
  ]
}

A stricter pass

Want a stricter pass than a single shell?
Add a second run inside a real job and diff the two receipts.
Same script, two hosts, no values in either file.

diff -u scratch-context-gap.json job-context-gap.json || true

I label the scratch file as untrusted context.
I label the job file as job context only.
I still do not call either file a release receipt.

Four steps, no folklore

Four steps keep the check boring, which is the point.
Skip a step and you are back to folklore.
Run them in order on hosts you actually control.

  1. Copy the script and the name list into the scratch shell.
  2. Run the script and keep the JSON receipt local.
  3. Repeat inside one real job that already has context.
  4. Diff the receipts and write the conclusion in the review.

Do not paste the receipt into chat if a token state says present.
Say that the job token is present, then stop.
The file path is enough for the reviewer.

How I decide from the receipt

I read the receipt before I read the drafted job.
The table is the whole decision rule I use.
I do not add trust because the prose sounded sure.

Signal Conclusion Next step
CI_JOB_ID absent This process is not a GitLab job Do not claim job scope
Names present, job id absent Values were exported by hand Stop and purge that shell
docker_config file present Some auth file exists Do not print it; ask who made it
CI_JOB_TOKEN present A job context is actually here Keep the token out of chat
All listed names absent Scratch shell, as expected Draft stays unproven
Job receipt differs from scratch Context did not ride along Cite the diff, not the vibe

Three outcomes cover the draft reviews in front of me.
Scratch-absent means the draft has no group facts yet.
Hand-exported means someone leaked scope into the wrong host.

Job-present means you can discuss that job, and only that job.
A config file can exist and still lack your registry.
Ask who created the file before you call it a login.

The model I want in the room

Context is a grant, and grants are scoped.
A grant names the project, the ref, the environment, and the job.
A hostname names a machine, which is a different fact.

A pasted YAML file is not variable injection.
A model memory is not a protected environment.
A cache key string is not a cache hit.

I separate job process, registry auth, cache store, and environment rule.
Which of those four buckets did this draft actually touch?
If you cannot point at one, you are still guessing.

What this check cannot prove

Presence is not correctness, and absence is not an incident.
A present name can still hold the wrong scope or a stale value.
I never print the value to settle that argument.

An empty string counts as absent in this script.
That choice avoids printing blanks that look like secrets.
If you need empty versus unset, extend the check locally.

This script does not call the GitLab API.
It cannot see protected-branch rules from outside a job.
It cannot prove a cache hit on a shared runner.

Free model access can draft the script, then drift from the docs.
The free server option is a workspace claim, not a duration promise.
I do not know your quota, hardware, or retention window.

Neither should you invent those numbers in a review.
Do not paste production tokens into a scratch shell to be thorough.
Would you rather stay incomplete than expose a token?

The name check rejects odd strings, and that is only a guard.
It is not a secret scanner and not a policy engine.
Pair it with your real secret controls if you have them.

Who should skip this

Skip this if you need a signed release receipt.
Skip this if your team forbids any off-runner shell.
Skip this if the next step is a production deploy approval.

This workflow is a myth check for drafters and reviewers.
It is not a control for auditors or release managers.
Use the real job log when the decision can spend money or ship.

Before you bless the draft

I run the receipt, read the absent states, and leave the YAML unblessed.
That small habit cuts fake certainty out of the thread.

Already drafting on a free server with a free model?
Start with this receipt, not with a merge comment.

Comments (0)

Sign in to join the discussion

Be the first to comment!