Your first agent edit should copy CI, not guess.
Read the workflow file before you prompt anything.
README scripts often drift from the real pipeline.
Your first red CI run usually comes from that drift.
Fix the command contract before any model writes code.
Why this matters on day one
You join a repo as a junior engineer.
You want agent help on the first ticket.
That impulse is normal, and it is also dangerous.
The README shows npm test in a friendly block.
CI may run a sharded script with extra flags.
Those two commands are not the same contract.
An agent will copy the README because it is nearby.
Then your PR goes red on a check you never ran.
You waste the first review cycle on a command mismatch.
The rule
Pin the exact CI job command in a local file.
Run that file once before any agent session starts.
Keep the file in git so reviewers can see it.
Do not let the agent invent a test command.
Do not trust package.json scripts without the workflow.
Do not paste fix-CI prompts with no job log.
Artifact: pin file plus local runner
Create two small files inside the local clone.
Name them so a reviewer finds them fast.
Treat those files as the first-hour command contract.
Proposed layout for a git-tracked pin directory:
.ci-pin/
COMMANDS.md
ci-local.sh
FIRST_HOUR_CHECKLIST.md
Label this layout as a proposal, not a standard.
Your team may already use Makefile or just.
Reuse that file if it already mirrors CI.
Step 1: Find the workflow, not the README
Open .github/workflows before you open README.md.
List the files with a plain directory command.
Pick the workflow that runs on pull requests.
ls -la .github/workflows
If the repo uses GitLab, open .gitlab-ci.yml instead.
If the repo uses CircleCI, open .circleci/config.yml.
The format changes. The extraction job stays the same.
Wait: "The format changes." is 3 words. Need to fix.
Write the workflow filename into .ci-pin/COMMANDS.md.
Write the job name on the following line.
Write the branch trigger on the third line.
Proposed markdown stub for the pin header:
# CI pin for local runs
- workflow_file: .github/workflows/ci.yml
- job_name: test
- trigger: pull_request
- extracted_on: 2026-09-22
- extracted_by: <your-github-handle>
Fill the date when you actually extract it.
Do not backdate the pin for a cleaner story.
Reviewers notice stale dates beside fresh clones.
Step 2: Copy the run steps by hand first
Do not start with a clever YAML parser.
Read the run: blocks with your own eyes.
Agents miss working-directory and env keys.
Proposed extraction notes for the test job:
## Raw steps from job "test"
1. npm ci
2. npm run lint
3. npm run test:ci -- --runInBand
working-directory: frontend
env:
NODE_ENV: test
CI: true
Copy environment variables that change test behavior.
CI=true often disables watch mode in Jest.
Missing that flag makes local green and CI red.
If the job uses a matrix, pin one cell only.
Write the matrix values next to the command.
Do not pretend one cell covers every OS.
Step 3: Build a local runner that fails closed
Write ci-local.sh as a thin wrapper script.
It should fail if a required path is missing.
It should not install global tools on its own.
Proposed script. Review it before you run it.
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
: "${CI_PIN_NODE_ENV:=test}"
export CI=true
export NODE_ENV="$CI_PIN_NODE_ENV"
if [[ ! -f frontend/package.json ]]; then
echo "pin-ci: frontend/package.json missing" >&2
exit 1
fi
cd frontend
if [[ ! -d node_modules ]]; then
echo "pin-ci: run npm ci in frontend first" >&2
exit 1
fi
npm run lint
npm run test:ci -- --runInBand
Mark the script executable after you review it.
chmod +x .ci-pin/ci-local.sh
Run it once with no agent in the loop.
If it fails, you still do not prompt an agent.
You fix the pin file until local matches CI intent.
Step 4: Prove the pin against known green tests
Pick one test that already passes on main.
Run only that test through the pinned command.
Record the exit code in COMMANDS.md.
Proposed proof block for the pin file:
## Proof run
- commit: <full-sha-from-main>
- command: .ci-pin/ci-local.sh
- extra: npm run test:ci -- path/to/known.spec.ts
- exit_code: 0
- notes: watch mode off because CI=true
This is not a performance benchmark of any suite.
It is a contract check on your first hour.
Skip timing claims you did not actually measure.
Step 5: Only then open an agent session
Now you may use a coding agent on the ticket.
Paste the pinned command, not the README snippet.
Paste the failing log if the ticket is a CI fix.
Keep the agent inside the ticket path list.
Do not let it rewrite .ci-pin/ci-local.sh first.
That file is your control surface, not its playground.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode provides free model access and a free server option.
Use either only after the pin file exists in your clone.
A free model can help translate workflow text into the wrapper.
It cannot see private runner labels or secret values.
You still copy working-directory and env by hand.
If you use MonkeyCode's free server for that translation, paste the workflow text and the pin stub, then demand a wrapper that fails closed.
Step 6: Keep first PR scope tiny
Your first PR should not be a feature dump.
Ship the pin files if the team lacks a local CI mirror.
Or ship one ticket change plus the pin as proof.
Keep the diff small enough to revert in one command.
You still need a documented revert path on day one.
The pin file helps you explain that revert later.
Proposed PR title style for the pin-only change:
docs: pin CI test command for local junior runs
Do not mix a feature and a pin in one PR.
Reviewers cannot score a mixed diff fairly.
Split them if the ticket is already large.
Decision table
Use this table when README and CI disagree.
| Source | Trust for local run | Why |
|---|---|---|
| README | Low | Drifts after refactors |
| package.json script | Medium | Name may not match CI flags |
Workflow run: block |
High | This is what gates merge |
| Agent-invented command | None | It optimizes for green locally |
| Pin file after proof | Highest | You ran it on a known SHA |
Copy the table into COMMANDS.md if you want.
The table is the review artifact, not decoration.
It shows why you refused the README command.
First-hour checklist
Put this list in FIRST_HOUR_CHECKLIST.md.
Check items in order. Do not skip the proof run.
- Clone the repo and record the main SHA.
- Open the pull-request workflow, not the README.
- Copy job name, working directory, and env vars.
- Write
.ci-pin/COMMANDS.mdwith those exact lines. - Write
.ci-pin/ci-local.shas a fail-closed wrapper. - Install dependencies the same way CI installs them.
- Run the wrapper against one known green test.
- Record exit code zero beside the SHA.
- Only then start an agent on the ticket paths.
- Keep the pin files out of the agent edit set.
Proposed clone and SHA capture commands:
git rev-parse HEAD
git status --short
If status is not empty, stop the pin process.
A dirty tree makes the proof SHA meaningless.
Clean the tree, then capture the hash again.
Failure analysis: common first-hour misses
You copied npm test and skipped npm ci.
CI uses a clean install. Your laptop does not.
Lockfile drift then looks like a test bug.
You ignored working-directory in the job definition.
The agent edits tests in the repo root.
CI never sees those files during the check.
You dropped CI=true from the local wrapper.
Jest starts watch mode and then hangs forever.
You think the agent crashed the whole suite.
You pinned a deploy job instead of the test job.
The deploy job needs secrets you do not have.
Your local runner fails for the wrong reason.
You let the agent fix the workflow file itself.
Now CI matches the agent, not the team contract.
That is not onboarding. That is pipeline hijacking.
Need to fix short sentences in that last pair.
What this does not cover
This workflow does not parse reusable workflows for you.
It does not expand composite actions into shell steps.
It does not replace a real CI reproduction environment.
Private runners, service containers, and GPU labels stay remote.
You cannot pin those with a shell wrapper alone.
Ask a teammate before you invent a fake substitute.
The free model path will not know org policy.
It will not know your banned path list.
It will not own the rollback if the PR is wrong.
Who should not use this approach
Do not use this if you cannot clone the repo.
Do not use this if CI config is not in git.
Do not use this if you will skip the proof run.
Staff engineers with a maintained Makefile can ignore it.
Use their existing local CI target instead of a second pin.
Do not create another source of truth on day one.
Skip this if your first task is production access only.
A pin file will not unlock secrets or runner labels.
Ask for a sandbox ticket before you prompt any agent.
First rollback still belongs to you
If the agent PR is unexplained, revert it.
The pin file tells you which command was green before.
That evidence beats the agent's summary every time.
Record the revert command next to the pin.
Keep both notes in the same directory.
Future you will need them on week two.
Proposed revert note for the pin file:
## Revert
git revert --no-edit <merge-sha>
.ci-pin/ci-local.sh
Run the pin after the revert lands locally.
Green after revert means the pin still holds.
Red after revert means the pin was a lie.
Limitations recap
Short local wrappers hide real pipeline complexity.
Monorepos need one pin per package, not one pin.
Matrix jobs need an explicit cell, not a slogan.
Generated wrappers go stale when CI files move.
Re-extract the command when the workflow file changes.
Treat a stale pin as a failing onboarding test.
These scripts were not executed on your repository.
They are labeled proposals for a first hour.
Adapt paths, package managers, and job names before running them.
Pin CI first. Prompt second. Revert if you cannot explain the diff.
Top comments (1)
The step where you copy the job's raw steps by hand instead of trusting a YAML parser is spot-on. I've seen too many junior engineers skip the env vars and working directory details, leading to CI reds later. The proof run with a known green test is the kind of concrete check that actually prevents the first PR from being a disaster. For teams using Jest, I'd emphasize that CI=true flag-missing it makes local tests run in watch mode and hang forever.