Chapter 21. This Machine Is a Stale Mirror
An AI coding agent describes code that stopped being true when its
checkout stopped being current, and a stale checkout raises no error to
warn it. On 2026-06-08 the main checkout of Suede-AI-App on
this machine sat 458 commits behind origin/main, on an
abandoned feature branch with a paused rebase, and what an agent read
out of it was that far out of date. Two computers run Suede. One of them
is where I work and push; whatever it pushed to GitHub last is the truth
of the estate. The other is the Mac with home directory
/Users/jasoncolapietro, and it holds copies: 60 git repos
under ~/code, 52 task worktrees spread across 33 sibling
directories, and whatever some session parked on a branch and walked
away from. The copies look like the estate. They carry none of its
authority. The discipline on this box comes down to getting a fleet of
agents to hold both facts at once: the code here is real, and the state
here is not.
What 458 commits behind origin/main looks like
On 2026-06-08 the main checkout of Suede-AI-App sat on a
feature branch named feat/nft-collectibles-gallery. It was
458 commits behind origin/main. The checkout also carried a
paused REBASE_HEAD and uncommitted changes on top of the
stale branch. The worktrees around it ranged from 48 to 449 commits
behind, and several other checkouts on the machine were parked on dead
feature branches of their own. None of this was damage. The other
computer had kept pushing while this one sat still, and 458 commits is
what sitting still costs at the pace the estate moves.
The number is survivable. The danger is what an agent does with a
checkout like that when it treats what it sees as current. It reads
stale code and reports features that no longer exist. It cuts a branch
from local main and hauls hundreds of commits of drift into
its diff. It re-fixes a bug the other machine fixed long since, opens a
PR that resurrects a deleted file, or deploys the stale tree to
production with a straight face. A stale mirror produces no error
message. It answers questions with information that stopped being true
the moment the other computer pushed past it.
Why this machine is a mirror and not the source
The split is deliberate: the other computer authors the estate, and
this one runs agents, several at a time. Concurrent sessions are the
operating condition here, not an exception. In one read-only pass taken
while I drafted this chapter, pgrep -x claude | wc -l
returned 6: six Claude Code processes running at once. At 18:31 that
day, 3 hours 40 minutes after boot, load averages read 16.28, 11.03,
7.92. The one-minute figure sitting at double the fifteen-minute figure
means the fleet had ramped up within the previous few minutes rather
than idled at that level all afternoon.
Concurrency is also why a shared checkout cannot serve as a
workspace. On 2026-06-10 I was editing ~/code/Suede-AI-App
when my uncommitted changes vanished mid-task; two edits in, everything
was gone. Another live session had switched the shared checkout from
main to its own branch,
feat/master-registry-consolidation, and a branch switch by
any session wipes the uncommitted work of the rest with no warning. That
same session had also appended its own entry, named
registry-dev, to the shared
~/.claude/launch.json, which is how dev servers claim ports
here: one file, many writers, unique port per task or collisions follow.
A shared checkout is shared mutable state, and the collision mechanism
has nothing to do with which repo it is; I treat any busy checkout on
this machine the same way.
The fix became a standing convention. One worktree per task, cut in the sibling directory:
git -C ~/code/Suede-AI-App worktree add \
~/code/Suede-AI-App.worktrees/<task-name> -b <branch> origin/mainThen copy frontend/.env.local over from the main
checkout, because it is gitignored and dev needs it. The last argument
carries the whole discipline: the branch cuts from
origin/main, the ref a fetch refreshed moments earlier, not
from a local main that might be 458 commits in the
past.
The disk shows how hard the convention gets used. ~/code
holds 64 top-level directories once the worktree siblings are excluded;
60 are git repos and 4 are not (INBOX, backups, handoffs,
the-signal-chain.fleet). Beside them sit 33 *.worktrees
parent directories holding 52 worktrees.
Suede-AI-App.worktrees alone holds 21;
suede-promo.worktrees holds 8;
suede-voice.worktrees holds 4. And 19 of the 33 parents are
empty, shells left over from earlier cleanups. Worktrees here are
disposable on purpose: cut from a fresh ref, used for one task, removed
when the task lands.
The two commands that measure staleness
Before touching any repo on this machine, an agent runs two commands:
git -C ~/code/<repo> fetch origin
git -C ~/code/<repo> status --short --branchThe fetch refreshes what the mirror knows about origin. The status answers the three questions that decide what is allowed next: which branch is this checkout on, how far behind its remote has it fallen, and is there dirty work in the tree. The pair costs ten seconds. The 458-commit checkout is what skipping those ten seconds looks like, given time.
Which operations a stale mirror makes unsafe
Push, merge, and deploy are the operations a stale mirror makes unsafe, and the rest follows from the single premise that GitHub and the other computer are authoritative and this box is not.
Never push, open a PR, merge, or deploy from a base that is behind
its origin/<branch>. Verify 0-behind first, and
verify it fresh; a check from the morning is void by afternoon if the
other computer pushed at noon. Never force-push from here. A force-push
from a mirror is the copy overwriting the original.
Cut new work from a fetched origin/main into a new
branch or worktree. Starting from a stale local checkout inherits the
drift plus whatever abandoned branch the checkout was parked on, and on
2026-06-08 this machine had several checkouts parked that way.
Preserve dirty work and paused rebases as found. The paused
REBASE_HEAD on feat/nft-collectibles-gallery
was not aborted, resolved, or stashed; I left it where it sat.
Uncommitted state on this machine can belong to another session, or to
intent the other machine never finished expressing. Not being able to
identify the owner is the reason to leave it alone, not a license to
tidy it.
Don’t run vercel deploy --prod from a local checkout.
The GitHub-to-Vercel integration ships what the authoritative side
pushed; a CLI deploy from here ships whatever this mirror happens to
contain and labels it current. When a manual deploy is unavoidable, it
comes from a worktree cut from origin/main moments
before.
The sandbox framing also settles where answers come from. When the
question is what a feature does in production, the answer lives at the
live URL or on GitHub, not in ~/code. A local tree can tell
you how something is built; it cannot tell you what is deployed. Agents
here quote the mirror for structure and the live surface for behavior,
and a claim about production backed by a local file read is treated as
no claim at all.
And the line that keeps the caution from freezing all work: local build, test, and read are fine. Push, merge, and deploy are the operations that can affect “current,” and they are the whole list. An agent can compile, run suites, and read code here all day at zero risk. The blast radius starts where bytes leave the box.
Why a one-time pull-all does not fix the problem
On 2026-06-10 I ran a pull-all that made this mirror current without
making it authoritative: I fetched and fast-forward pulled about 50
repos, everything then in ~/code. The fast-forward
constraint was the point, not a convenience. A fast-forward can restate
what origin says and nothing else; a merge pull would have had this
mirror manufacturing commits the authoritative side never wrote, which
is the one thing a mirror must not do even while catching up.
Suede-AI-App came out clean on current main.
No paused rebases remained in any main checkout. The sweep left the 4
broken-remote snapshots under ~/from-jasons-macbook
untouched. For a moment the mirror agreed with the source.
The rule did not relax, because the rule is about the process that produces drift, not about any one measurement of it. This box falls behind again the moment the other computer pushes. A sync makes a mirror current; it does not make it authoritative. Re-verification is per-action: prove 0-behind before this push, then prove it again before the next one. The estate keeps growing under the mirror’s feet too; the directory that held about 50 repos on 2026-06-10 holds 60 as I write this.
Why synced is the wrong word for a fetch
“Synced” is a status claim on this machine, an assertion that local
now represents current, and a fetch earns no such claim. A routine
git fetch once ran here and alarmed me, and I stand by the
alarm. On semantics I had no case: fetch mutates nothing remote, and it
is the first command in this chapter’s own procedure. The framing was
the problem, because the agent presented the operation as syncing. The
instruction I gave the fleet afterward: “make sure work we do on this
computer doesn’t affect anything as if it were current.”
The fetch stayed; the word went. Agents here report the operation as what it is: refreshed remote refs, read-only, nothing pushed. I make push and deploy decisions off the verbs in an agent’s report, so on a box whose defining property is that it does not represent current state, the verbs are load-bearing. A report that says “synced” when it means “fetched” is not wrong about the git objects. It is wrong about what I am now entitled to believe.
Three checkouts as found, and what each one proves
While drafting this chapter I took a read-only snapshot of three active repos on this machine. No fetch ran as part of it.
| Repo | Last local commit | Branch | Working tree |
|---|---|---|---|
Suede-AI-App |
965ba6e1, 2026-07-26T17:46:33-04:00, “feat(social):
hand the saved rig off to the Registry (#796)” |
main |
dirty, 10 entries |
suede-agent-studio |
badcd29, 2026-07-24T01:09:58-04:00, “copy: finish the
payout-promise sweep on non-pricing surfaces (#151)” |
detached HEAD | dirty, 2 entries |
strumly |
b70714d, 2026-07-26T00:23:48-04:00, “fix(tools): lead
with the interactive trainer, not the reference tables” |
main |
clean |
Each row exercises a different clause of the discipline.
Suede-AI-App sits on main with a commit
dated 2026-07-26 and ten dirty entries: two modified App Store metadata
files under ios-app/fastlane/metadata/en-US/, and eight
untracked paths including a CLAUDE.md, fastlane metadata
directories, and product docs plus two images under
suede-home/. The fresh commit date proves the mirror was
touched that day; it proves nothing about distance from origin. The
dirty entries are some session’s work in flight, so the preserve rule
applies, and nothing gets stashed or cleaned on its behalf.
suede-agent-studio is the instructive row. Detached
HEAD, no branch at all, with a modified src/app/landing.css
and an untracked fastlane/ directory sitting on top. Dirty
work with no branch anchor is one careless git checkout
away from being hard to find again. An agent arriving here to start a
task has one correct move: leave this checkout as it stands and cut a
worktree from origin/main beside it.
strumly is the tempting row. Clean tree, default branch,
a commit from 2026-07-26. It looks safe, and the appearance is worth
nothing, because the snapshot ran without a fetch and the timestamps
describe the local copies and nothing beyond them. The distance from
this main to origin/main was unmeasured at the
moment of the snapshot, and unmeasured means assumed behind. On this
machine a clean checkout is not a green light. It is a checkout whose
staleness has not been measured yet.
The discipline in one paragraph
GitHub and the live URLs hold the truth of the estate; the other
computer writes it; this machine keeps copies for agents to build and
test against. Fetch and read status before touching any repo. Prove
0-behind before any push, merge, or deploy, and prove it again for the
next one. Cut branches from origin/main, not from a local
main nobody has measured. Leave dirty trees and paused
rebases as found. Two commands and ten seconds per repo buy all of it,
and the 458-commit checkout with the paused rebase is the standing
exhibit of what accumulates when they get skipped.