All posts
ai industry security

The instructions were on disk and the agent never read them

Security Engineer
Security Engineer · Engineer
September 25, 2026 · 6 min read

On September 23 a developer published a small experiment that deserves more attention than the fix it prompted. Claude Code 2.1.277 had added support for AGENTS.md, the vendor-neutral instruction file that a growing number of coding agents read before doing anything else. He put a canary word, PERIWINKLE, into an AGENTS.md file, asked the agent to repeat it back, and watched the word not come back. Whenever telemetry was disabled, the file was never read. No warning, no error, no degraded-mode notice. The instructions were on disk and the agent behaved as if they did not exist.

The mechanism, traced in the 2.1.280 bundle, is worth spelling out. The AGENTS.md loader shipped behind a remote feature flag named tengu_agents_md_mod. With DISABLE_TELEMETRY=1 or CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 set, the client never fetches feature flags, and an unfetchable flag falls back to false. In the author’s words: when the client cannot fetch the flag, the plugin is unavailable, and the local file is never read. A privacy preference on one channel quietly switched off unrelated local behavior on another.

Anthropic’s response was fast and, to its credit, plain. An engineer explained on the spot that this was a rollout artifact: a risky launch wants a remote kill-switch, kill-switches ride the feature-flag system, and with telemetry off there are no flags. The fix shipped in 2.1.281 within a day, the loader’s source was published, and the engineer called it a fully human error. We believe all of that. The fix is still not the story. The bug, in the window it existed, proved several things about how instruction loading works, and those things stay true after the patch.

A missing instruction does not error

We wrote in September about what earns a place in the file agents read first, and our answer was boundaries: the limits of safe autonomous action, the things an agent must not do without asking. This incident is the other half of that argument. A boundary that is silently not installed produces no failure signal at all. The run proceeds. The output is different from what an instructed agent would have produced, but it is different in ways that look plausible, because the agent is doing its competent best with what it was given.

That is what makes this class of failure worse than a crash. A tool that cannot execute fails loudly and the loop stops. An instruction file that fails to load subtracts a control and adds nothing in its place. Hundreds of thousands of people were running the affected versions, and as far as anyone can tell, not one of them noticed by observing agent behavior. The bug was found by someone deliberately probing the assumption everyone else was making. That ratio, one deliberate probe against a sea of trusting sessions, is the number that should worry us.

The effective agent is not the files on disk

What actually governed those sessions was not the repository. It was the intersection of the repository, a remote flag service, the rollout state of a plugin, and a local environment variable that was supposed to be about privacy. Two developers with identical checkouts of the same repo were running differently instructed agents, and nothing in either working tree could reveal the difference.

The discussion around the incident surfaced a second silent conditional in the same layer: AGENTS.md is skipped entirely when a CLAUDE.md exists anywhere up the chain, unless a non-default project setting says to read both. That one is documented behavior rather than a bug, but it has the same shape. Whether the file you wrote is part of your agent’s context depends on state that is not in the file and not next to it.

We take two working rules from this. First, the context an agent actually assembled is a fact about a run, not a property of a repo, and it belongs in the run’s record. We log which instruction files were loaded, at what size, the same way we log which tools were called. Second, any capability that arrived recently should be presumed to sit behind a switch somewhere, and it is fair to ask a vendor where that switch lives and what happens when it cannot be reached.

Fail-closed for the feature is fail-open for the boundary

The engineering instinct behind the default was conservative: if the flag cannot be fetched, turn the feature off. For most features that is right. But when the feature is instruction loading, failing the feature closed fails the control open. The conservative default removed the file that carries the rules. We made a similar observation about approval gates in August: a control that degrades silently is worse than no control, because everyone downstream is still behaving as if it is there.

The coupling matters as much as the default. The kill-switch rode the telemetry channel, so the users who got the degraded behavior were precisely the ones who had expressed a preference for less remote coupling. They opted out of reporting and were opted out of behavior they could see no connection to. Control planes and reporting planes deserve separate failure analyses, because users will sever one and expect the other to stand.

The canary is now part of our checklist

The probe that found this bug cost one word in a file and one question to the agent. That is the whole technique. We have adopted it as standard practice: a canary token in the root instruction file, and a session-start check that the agent can produce it. If the canary does not come back, the session stops before any real work starts. It is the cheapest liveness test we know of, and this month it caught what a half-million installs did not.

The patch took a day. The durable lesson is the distinction it exposed: the presence of a control file proves nothing about the presence of the control. An instruction is only in effect when the agent can demonstrate it received it, and demonstrating that is now something we do on purpose, every session, rather than something we assume because the file is sitting right there.