Skip to content

9 min read

Read the assembled configuration before believing the file

On this page

The message in the platform channel is four lines long and completely reasonable. Priya has added a line to the repository's instruction file saying that this project uses pnpm and the agent should never invoke npm, the line is unambiguous, the file is committed on main, and the agent has run npm install in three of her last five sessions. She attaches the file. The file is fine. Everyone in the thread reads the file, agrees the file is fine, and suggests rewording it.

Rewording is the fifth thing to try, and teams start there because the file is the artefact they have to hand. The file is a source. What the model saw is the assembled configuration for that session: the concatenation of every layer the host decided to include, in the order it included them, after whatever truncation the size budget forced. Between the line on disk and that assembly there are at least four ways for it to vanish or be outvoted, and none of them are visible from the file.

Three checks, cheapest first

The order matters because each check eliminates a whole class of cause, and the earlier ones cost seconds.

The first check is whether the instruction is present in the assembled context at all. That sounds too obvious to deserve a step, and it is the answer roughly a third of the time. The file was added on a branch that has not merged. The host reads AGENTS.md and Priya edited CLAUDE.md. The file sits at apps/api/ and she has been launching sessions from the repository root, which under the nearest-file model from chapter 1 means it was never in scope. The assembled text hit a size limit and her section was in the part that got dropped. If the words are not there, every explanation you might construct about how the model weighed them is fiction.

Truncation deserves its own note, because it is the failure mode that scales with the team's diligence. Hosts generally cap the instruction budget somewhere between two and eight thousand tokens, and a repository that has been accumulating conventions for a year will pass that quietly. The file that gets trimmed is not the least important one; it is whichever one the loader reached last, which in a cumulative scheme is usually the deepest and most specific. A team that adds a 900-token architecture section to the root file on a Tuesday can push the tail of a package-level file out of context on the Wednesday, and the symptom is that a rule which worked for six months stops working with no change to the file containing it.

The second check is what else is present that speaks to the same subject. Presence is not sufficiency, because the assembled text may contain her line and also a line from another layer naming npm as the standard. This is the check that requires reading the assembled context rather than grepping it for pnpm, since a rule that contradicts her will not usually contain her keyword — chapter 1's point about prose having no key applies across layers exactly as it applied across directories, and only the origin of the second string has changed.

The third check is the wording, and only now. If the instruction is present and nothing else in context speaks against it, the question genuinely becomes whether "never invoke npm" reads as one preference among forty preferences, and strengthening or relocating the line is a reasonable response. Arriving here first means potentially spending a week improving the phrasing of a sentence that was never in the context to begin with.

bash
$ cd ~/work/platform/apps/api
$ claude
> /context

Context sources for this session
────────────────────────────────────────────────────────────────
  user       ~/.claude/CLAUDE.md                       412 tokens
  org        acme-engineering-policy (v14)           1,180 tokens
  project    platform/CLAUDE.md                        960 tokens
  project    platform/apps/api/CLAUDE.md               240 tokens
────────────────────────────────────────────────────────────────
  instructions total                                 2,792 tokens
  order applied: org → user → project → directory

Four sources, and Priya has read one of them. Her line is in platform/CLAUDE.md, and it is present. The acme-engineering-policy entry is 1,180 tokens of text she has never seen, distributed centrally and injected into every session at the company, and printing it turns up a line written some years earlier: all repositories standardise on npm so that lockfiles are reproducible in CI. Two instructions about package management, both present, one older and one nearer, and nothing anywhere stating which governs. The agent is not ignoring her. It is arbitrating, without a rule, on every run.

The last line of that output is the one people misread. order applied: org → user → project → directory describes the sequence in which the host pasted the sources together, and a sequence is not a precedence rule; it is a fact about string concatenation. Position does have some influence on how a model weights instructions, which is why a rule buried at token 400 of 2,792 behaves differently from the same rule at token 2,700, but the influence is statistical rather than dispositive and it varies with the model, the length of the surrounding text and the shape of the request. Reading the arrow as "later wins" is the single most common misinterpretation of this screen, and it will appear to be true for a while, because the later text does win more often than not.

Flowchart: org layer policy v14 · 1,180 tok → A; user layer ~/.claude · 412 tok → A; project root CLAUDE.md · 960 tok → A; directory apps/api · 240 tok → A; assembled context 2,792 tokens, ordered → model; assembled context 2,792 tokens, ordered → silently truncated tail (over budget); model → observed behaviour.

Everything on the left of that assembly is invisible in a code review of platform/CLAUDE.md, which is the artefact the thread was arguing about.

Deleting the org-layer line resolves this instance and answers neither of the questions worth answering. The first is whether the project layer was ever meant to win here, which is a decision with an owner: if the org line exists because a CI image only has npm on the path, Priya's repository is about to fail its builds for a reason nobody will connect to a chat thread. The second is why nothing surfaced the collision — the org text had been shipping into every session for months, and the only reason anyone read it was that a specific engineer got annoyed enough to escalate. Fix only the instance and the next stale org line reproduces the whole week.

The five-minute answer to the previous chapter's question

Chapter 1 ended with a probe that asks the model to report its own context, and noted the weakness: a model with no instruction files loaded will occasionally invent a plausible inventory. The assembled view has no such weakness, because the host is reporting what it sent rather than the model reporting what it remembers.

Maya's question was whether her monorepo does nearest-file or cumulative discovery. Start a session with the working directory set to packages/internal-tools/, print the assembled configuration, and count the project-layer entries.

bash
$ cd ~/work/platform/packages/internal-tools
$ claude
> /context

  project    platform/CLAUDE.md                        960 tokens
  project    platform/packages/internal-tools/CLAUDE.md  180 tokens

Two project entries, root first, is cumulative discovery, and the intermittency is expected. One entry naming only the nested file is nearest-file discovery, and a root rule leaking into that package is then a genuine defect worth reporting. One entry naming only the root file means the nested file was never discovered, usually a filename or a symlink problem. The same command run from the repository root tells you whether discovery is anchored to the working directory or to the checkout, which is the difference between Maya's behaviour changing when she cds and it staying put.

The equivalent for a team on a different host is whatever that host calls its diagnostics — the Debug your configuration page in the Claude Code documentation describes /context and /doctor for this purpose, and most agent runners have some flag that dumps the resolved prompt. If yours has none, the fallback is to run the agent under a proxy and capture the outbound request body, which is uglier but authoritative in a way nothing else is.

The engineer who can reproduce it

A deployment goes out without the approval step that the release runbook requires the agent to pause for. The incident review pulls three engineers who all run the same release workflow. Two of them cannot reproduce the skip across a dozen attempts. The third reproduces it every single time, on the same commit, with the same prompt, on the same host version.

Reproducibility that is perfect per-person and absent across people is not a model-variance signature, because model variance does not respect who is typing. Something in the assembled context differs, and the repository cannot be the thing that differs, because all three are on a1b2c3d. That leaves the layers that are not in the repository, and the user layer is the one that nobody else can read.

bash
$ diff <(ssh dev-a 'cat ~/.claude/CLAUDE.md') <(ssh dev-c 'cat ~/.claude/CLAUDE.md')
> ## Personal preferences
> Don't stop to ask me for confirmation on routine steps — I'll review the
> diff at the end. Prefer finishing the task over checking in.

Three lines, written eight months earlier by an engineer who was tired of being asked whether to continue, applying to every session that person runs in every repository on that machine. It is not a rogue action and there is no policy anywhere saying not to write it. The user layer exists precisely so that individuals can express preferences that do not belong to the project, and this one happens to intersect a release-critical instruction in one repository out of forty. The property that made it invisible is structural: a shared checkout implies shared behaviour only for the layers that live in the checkout.

The practical consequence for incident work is that "all three are on the same commit" is not a statement about the agent's inputs, and asking each participant to attach their assembled configuration to the ticket costs them thirty seconds and closes questions that otherwise take days.

LayerWho can read itWhere it is versionedApplies to
OrganisationUsually nobody on the teamAn admin console, if anywhereEvery session in the company
UserOnly that personNothingEvery session that person runs
Project rootAnyone with the repoGitSessions in that checkout
DirectoryAnyone with the repoGitDepends on discovery model

The middle two rows are where reproduction stories go to die, and they are the two rows that a code review of the repository cannot see.

What the assembled view does not settle

It tells you what was sent, which is not what governed. Priya's line and the org line were both present, and presence gave no indication which one the model would follow on a given run; chapter 1's changeset rules were both present too. The view is an inventory, not an explanation, and treating it as one leads to the assumption that whichever text appears last must have won.

It is also per-session rather than historical. The assembly that produced last Thursday's incident is gone unless the host wrote it somewhere, and the file it came from may have changed twice since. Teams that care about this record the assembled digest alongside the run — a hash of the concatenated text plus the source list is around 200 bytes and turns "we think the config was the same" into a comparison you can perform.

And it does nothing for the layers you cannot enumerate. If the org layer is delivered by a service whose current version you cannot print, the inventory tells you a policy of 1,180 tokens participated and not what it said, which is enough to know you are blocked and not enough to diagnose.

Making this one step rather than five is mostly organisational. A committed make agent-context target that prints the assembly and its source list, an org layer mirrored into the repository as a readable file that CI checks for drift, and a habit of pasting that output into any thread that begins "the agent is ignoring" — those three together move the median diagnosis from a week to a message. The next chapter takes up the question the inventory leaves open, which is what actually happens when two layers both have something to say: whether one replaces the other wholesale, or the two merge instruction by instruction, and how to find out with a test whose result you can recognise in the output.

Scoping and precedence — read the current version at https://daviddominguez.dev/learn/en/scoping-and-precedence/read-the-assembled-configuration-before-believing-the-file

Back to top