The instructions block is a snapshot from session start

Forge-and-Tooling: correct the mechanism. The block is not a live read of
the spawning session's working directory but a snapshot taken when that
session started, so fetching mid-session does not refresh it — the file
on disk can be correct while every agent started afterwards still gets
the superseded rules. Records the measurement that shows it: a directory
fast-forwarded and verified beforehand, an agent worktree identical to
origin/main (0 0), and a block from before Gitea #47 regardless. States
the two consequences that hold, separately from the ones that do not.

Lessons-Learned: a new entry, because the failure form is the one this
page collects — a stale instructions block looks exactly as
authoritative as a current one, like the probe that cannot fail and the
required array that never fires. Both measurements included, and the
point that it went well only because someone checked. One bullet added
to "The recurring shape".
jklein committed 2026-09-27 23:05:17 +00:00
1 parent 2c520fae77
commit 6fbc6d5776
2 files changed
+17 -4

No files matched your search

+8 -4
@@ -44,14 +44,18 @@ git switch -c <branch> origin/main
Never from the local `main` ref, and never on the assumption that the shared checkout is current. Both can be many merges behind, and nothing about them says so.
What lifts this above tidiness is how an agent receives its instructions. The **project-instructions block** a subagent is handed at the head of its context — the binding copy of `CLAUDE.md` — is read from the **spawning** session's working directory, not from `origin/main`. A stale worktree therefore presents every agent it starts with rules that were corrected two PRs ago, and the agent cannot tell: a stale block looks exactly as authoritative as a current one.
What lifts this above tidiness is how an agent receives its instructions. The **project-instructions block** a subagent is handed at the head of its context — the binding copy of `CLAUDE.md` — is a **snapshot** of the spawning session's working directory, taken when **that session started**. It is not a live read of the file, and nothing re-reads it afterwards: fast-forwarding the directory mid-session leaves the block exactly as it was, so the file on disk can be correct while every agent started from then on still receives the superseded rules. The agent cannot tell the difference, because **a stale block looks exactly as authoritative as a current one.**
Measured on 2026-09-27, twice on the same day:
Measured on 2026-09-27:
- The manager session's working directory hung several merges back. An agent started from it reported a "contradiction" in `CLAUDE.md` — the remote bullet — that had been fixed two PRs earlier, and quoted the stale text as the rule in force. Nothing broke, because that same agent had checked its diff against `origin/main` instead of against the local pointer.
- The manager session's working directory hung several merges back. An agent started from it reported a "contradiction" in `CLAUDE.md` — the remote bullet — that had been fixed two PRs earlier, and quoted the stale text as the rule in force.
- Later the same day, with that working directory deliberately fast-forwarded to the tip beforehand and the result checked ("Already up to date"), the next agent's block was **still** from before Gitea #47 — while the agent's own worktree was identical to `origin/main`, `git rev-list --left-right --count origin/main...HEAD` returning `0 0`. This is the measurement that identifies the block as a snapshot from session start, rather than merely a symptom of a stale checkout.
- In the shared checkout, `main..HEAD` showed **105 differing files**, while the difference against `origin/main` was a single line.
Hence the other half of the rule: **read diffs against `origin/main`, never against `main`.** It is the only comparison here that proves anything. A worktree that turns out to be behind is rebased onto `origin/main` before the work starts, not after — the first thing to run in a fresh worktree is the fetch and the comparison.
Two things follow, and only these two help:
- **The spawning session's working directory has to be current *before* the session starts.** Pulling afterwards is too late for every agent that session goes on to start.
- **An agent verifies a rule against the file in its own worktree, and against a diff with `origin/main`** — never against `main`, and never against its own context block. The first point cannot be enforced from inside an agent; this one can, and it is what kept both incidents above harmless. A worktree that turns out to be behind is rebased onto `origin/main` before the work starts, not after, which makes the fetch and the comparison the first thing to run in a fresh worktree.
## Signed commits are enforced
+9
@@ -186,6 +186,14 @@ It surfaced by accident. A pull request correcting a *comment* in 21 charts made
**Now:** the install-test job is one reusable workflow with two callers, and the second runs it over **every** chart on demand (Gitea #52) — after a runner change, a `K3S_IMAGE` or `kubectl` change, a fixture change, a forge move. Every leg records date, runner label, `K3S_IMAGE` and `kubectl` version next to its outcome, because a green run nobody notes is unknown again next time. See [CI/CD](CI-CD#the-all-charts-install-test-on-demand). Generalized: **a check whose scope follows the diff never re-checks what the diff does not touch** — and what it quietly stops covering is the environment itself.
## The instruction block no fetch could refresh
The rules an agent works under arrive as a **project-instructions block** at the head of its context — the binding copy of `CLAUDE.md`. It reads as the current file, and carries nothing that would suggest otherwise. It is a snapshot of the spawning session's working directory, taken when that session started.
On 2026-09-27 an agent reported a "contradiction" in `CLAUDE.md` — the remote naming — that two pull requests had already fixed, and quoted the stale text as the rule in force. The obvious explanation was a stale checkout, and that was half of it: in the shared checkout, `main..HEAD` showed **105 differing files** while the difference against `origin/main` was a single line. The sharper measurement came from the next agent the same day, after that working directory had been deliberately fast-forwarded and the result checked ("Already up to date"): its block was **still** from before Gitea #47, while its own worktree was identical to `origin/main` — `git rev-list --left-right --count origin/main...HEAD` returned `0 0`. A current checkout and stale instructions, at the same moment. Nothing broke either time, and the reason belongs to the lesson: both agents had checked their work against `origin/main` instead of against what they had been handed.
**Now:** worktrees are cut from a freshly fetched `origin/main`, diffs are read against `origin/main` and never against the local `main`, and an agent checks a rule against the **file in its own worktree** rather than against its context block. The spawning session's directory has to be current *before* it starts, because a fetch afterwards refreshes nothing that was already handed out. See [Forge and tooling](Forge-and-Tooling#cut-every-worktree-from-a-freshly-fetched-originmain). Generally: **a document delivered as context is a copy with an age, and the copy shows no sign of it** — so what a rule says has to be settled against the source, not against the copy.
## The recurring shape
- **Green is a claim about the assertions that ran, not about the system.**
@@ -194,4 +202,5 @@ It surfaced by accident. A pull request correcting a *comment* in 21 charts made
- **When a rename and a silent behaviour change are both available, take the rename.** Schema validation failing loudly beats a value that quietly means the opposite.
- **A safeguard is only a safeguard if it can fail.** Both the missing `required()` and the `required` array that replaced it read as protection; neither could ever have fired.
- **Write down what was measured, and say when something was not.** Half of this page exists because someone recorded a number instead of an impression.
- **A document handed to you as context is a copy, not the source.** Correcting the file does not correct the copies already delivered, and a stale copy looks exactly as authoritative as a current one — only the file itself, and its diff against `origin/main`, settle what a rule says.
- **An answer that arrived by accident is worth making repeatable.** The all-charts install-test exists because one unplanned run produced a result nobody could have asked for; the fix is a way to ask the question on purpose, not a note to remember the accident.