spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

mods/agents-md: a Read attaches no nested AGENTS.md where the engine attaches nothing to a turnBrowse files
poteat authored on Sep 18, 2026 parent a92ea1c · commit 6dc0ce9120d8
6 changed files+61 -5compared with a92ea1c
Mmods/agents-md/README.md
Mmods/agents-md/hooks/hooks.json
Mmods/agents-md/hooks/index.ts
Mmods/agents-md/hooks/register.ts
Amods/agents-md/hooks/switches/index.ts
Amods/agents-md/hooks/switches/is-switched-on.ts
@@ -66,7 +66,12 @@
6666 compaction) carries the new mode's files. A hand-typed value outside the
6767 four is told once in the transcript and reads as the default. `/plugin`
6868 lists the plugin among the built-ins, where a person can turn it off; with
69-it off the engine reads `CLAUDE.md` alone.
69+it off the engine reads `CLAUDE.md` alone. No hooks setting or CLI mode turns
70+it off (`disableAllHooks`, `allowManagedHooksOnly` and `--bare` govern
71+settings hooks and installed plugins, not built-ins); where the engine loads
72+no instruction files (`--bare` without `--add-dir`, `--safe-mode`,
73+`CLAUDE_CODE_DISABLE_CLAUDE_MDS`) its walk finds none and it adds none,
74+`CLAUDE.md` and `AGENTS.md` alike.
7075
7176 The option was first keyed `projectInstructions`, with the values `claude`,
7277 `agents-fallback`, `both` and `none`. A value still stored under that key is
@@ -88,7 +93,7 @@
8893 | `session.start` | in every mode: passes the start straight through and floats the usage row for the configured mode, never awaited; the first start of a load logs how a stored `projectInstructions` value is read. The session's start never waits on this plugin |
8994 | `prompt.context` | under `claude-md-or-agents-md` and `claude-md-and-agents-md`: walks `$.fs.ancestors` for the `AGENTS.md` files above the working directory and answers them as `project` instruction files, each `@` import its own entry after its file, each placed where a project file of its directory stands (root first, before the first deeper project file, else after the last project file, before memory); files the engine already holds by path or by content are left out; under `claude-md-or-agents-md` it answers nothing when the project has a `CLAUDE.md` of its own (among the handed files, else found by a `$.fs.ancestors` walk, so a `CLAUDE.md` the engine loaded and then withheld still counts), and logs which files it loaded once, and again after a move to another project root; handed unknown files (a hook above rewrote the `claudeMd` text) it adds nothing; the first context of a load sends the load row (counts) and the feature mark; under `managed-only` (matcher: a `project`, `local` or `user` file present): answers the list without those kinds |
9095 | `agent.spawn` on `fork: true` | under `claude-md-or-agents-md` and `claude-md-and-agents-md`: a fork the Agent tool starts shares its parent's prompt prefix, so the parent loop's delivered nested files are copied to the fork's loop and not attached to it again (a `/fork` or `/subtask` fork does not raise `agent.spawn` yet and starts from an empty set, as every fork did before; a fork started in the same tool batch as a `Read` inherits that Read's file although its prefix holds a placeholder for it) |
91-| `tool.call` on `Read` | under `claude-md-or-agents-md` and `claude-md-and-agents-md`, for a file under the session's project root (`$.session.root()`, read live, so `/cd`, a host's directory change and worktree moves are followed and a moved root starts the delivered sets and the fallback decision over; a file elsewhere gets nothing, as the engine attaches no nested `CLAUDE.md` there): walks only the directories strictly between the root and the read file (`$.fs.ancestors` with `below: root`, as the engine walks only those for a nested `CLAUDE.md`, never up to the filesystem root again) and attaches their `AGENTS.md` files not yet given to that agent loop, not already among the context's instruction files (by path or, for a project file, by text) and not claimed by a `CLAUDE.md` of the same directory (or imported by one), as `context` after the tool result, framed `Contents of <path>:` byte for byte as the engine frames a nested `CLAUDE.md`, whatever its size; each file once per loop and conversation (the context's recomputation after a compaction or `/clear` starts the count over), the context's files never; a Read that attached files sends the nested row. A `~` or `~/` path is read under the home directory as the Read tool reads it |
96+| `tool.call` on `Read` | under `claude-md-or-agents-md` and `claude-md-and-agents-md`, for a file under the session's project root (`$.session.root()`, read live, so `/cd`, a host's directory change and worktree moves are followed and a moved root starts the delivered sets and the fallback decision over; a file elsewhere gets nothing, as the engine attaches no nested `CLAUDE.md` there; and nothing anywhere in a run where the engine attaches nothing to a turn, `--bare` with its `CLAUDE_CODE_SIMPLE` or `CLAUDE_CODE_DISABLE_ATTACHMENTS`, read on every Read through `$.env.get` as the engine reads them on every turn): walks only the directories strictly between the root and the read file (`$.fs.ancestors` with `below: root`, as the engine walks only those for a nested `CLAUDE.md`, never up to the filesystem root again) and attaches their `AGENTS.md` files not yet given to that agent loop, not already among the context's instruction files (by path or, for a project file, by text) and not claimed by a `CLAUDE.md` of the same directory (or imported by one), as `context` after the tool result, framed `Contents of <path>:` byte for byte as the engine frames a nested `CLAUDE.md`, whatever its size; each file once per loop and conversation (the context's recomputation after a compaction or `/clear` starts the count over), the context's files never; a Read that attached files sends the nested row. A `~` or `~/` path is read under the home directory as the Read tool reads it |
9297
9398 ## What it calls on `$`
9499
@@ -97,7 +102,8 @@
97102 workspace files are remote, as the engine's own walk does), `session.root`,
98103 `session.cwd`, `env.get` (`HOME` and `USERPROFILE`, once per load, the
99104 profile first on a Windows spelling of the working directory, so a `~/` path
100-the model hands a Read resolves where the Read tool reads it), `ui.log`,
105+the model hands a Read resolves where the Read tool reads it; `CLAUDE_CODE_SIMPLE`
106+and `CLAUDE_CODE_DISABLE_ATTACHMENTS` on every Read), `ui.log`,
101107 `telemetry.log` and `telemetry.mark`.
102108
103109 `$.telemetry` is the [telemetry](../telemetry) plugin's noun; where that
@@ -1,4 +1,4 @@
11 {
2- "description": "AGENTS.md up and down the tree like CLAUDE.md, by the instructionFiles option: under claude-md-or-agents-md (the default; where the project has no CLAUDE.md of its own on the walk) or claude-md-and-agents-md, a prompt.context hook hands every ancestor AGENTS.md to the engine among its instruction files, skipping ones it already loaded, and a tool.call hook on Read attaches the AGENTS.md files of the directories between the project root and a read file, once a conversation and agent loop (an Agent-tool fork inherits its parent's); under managed-only, a prompt.context hook keeps only the organization's managed files and memory; under claude-md, nothing is added; in every mode a session.start hook sends the plugin's usage row through $.telemetry where that noun is seated",
2+ "description": "AGENTS.md up and down the tree like CLAUDE.md, by the instructionFiles option: under claude-md-or-agents-md (the default; where the project has no CLAUDE.md of its own on the walk) or claude-md-and-agents-md, a prompt.context hook hands every ancestor AGENTS.md to the engine among its instruction files, skipping ones it already loaded, and a tool.call hook on Read attaches the AGENTS.md files of the directories between the project root and a read file, once a conversation and agent loop (an Agent-tool fork inherits its parent's), except in a run where the engine attaches nothing to a turn (--bare, CLAUDE_CODE_DISABLE_ATTACHMENTS); under managed-only, a prompt.context hook keeps only the organization's managed files and memory; under claude-md, nothing is added; in every mode a session.start hook sends the plugin's usage row through $.telemetry where that noun is seated",
33 "modules": ["./register.ts"]
44 }
@@ -3,6 +3,7 @@
33 export * from './modes'
44 export * from './names'
55 export * from './register.js'
6+export * from './switches'
67 export * from './telemetry'
78
89 export * as default from '.'
@@ -10,6 +10,7 @@
1010 import Frames from './frames'
1111 import Modes from './modes'
1212 import Names from './names'
13+import Switches from './switches'
1314 import Telemetry from './telemetry'
1415
1516 /**
@@ -26,7 +27,9 @@
2627 * standing alone. `managed-only`: the project's and the person's instruction
2728 * files dropped, the organization's kept. `claude-md-or-agents-md` (a project
2829 * with none of its own) and `claude-md-and-agents-md`: AGENTS.md files joined
29- * to the engine's instruction files, nested ones on a Read. Every mode sends
30+ * to the engine's instruction files, nested ones on a Read except in a run
31+ * where the engine attaches nothing to a turn (--bare, which sets
32+ * CLAUDE_CODE_SIMPLE, or CLAUDE_CODE_DISABLE_ATTACHMENTS). Every mode sends
3033 * its usage rows through `$.telemetry` where that noun is seated and drops
3134 * them where it is not.
3235 *
@@ -182,6 +185,10 @@
182185 return result
183186 }
184187
188+ if (!(await attachesOnRead($))) {
189+ return result
190+ }
191+
185192 const [root, cwd] = await Promise.all([$.session.root(), $.session.cwd()])
186193 home ??= await homeOf($, cwd)
187194 const read = Frames.absoluteOf(e.file_path, cwd, home)
@@ -248,6 +255,31 @@
248255 }
249256
250257 /**
258+ * Whether a Read attaches nested AGENTS.md files in this run: not where the
259+ * engine attaches nothing to a turn, a nested CLAUDE.md included, which is a
260+ * --bare run (it sets CLAUDE_CODE_SIMPLE) or one with
261+ * CLAUDE_CODE_DISABLE_ATTACHMENTS on.
262+ *
263+ * Read on every Read, as the engine reads them on every turn: a settings
264+ * `env` block or a managed delivery can flip either mid-session. The files of
265+ * the walk itself need no such check: where the engine loads no instruction
266+ * files `$.fs.ancestors` finds none.
267+ *
268+ * @param $ the engine, as the `tool.call` hook holds it
269+ * @returns whether nested files ride a Read's result here
270+ */
271+async function attachesOnRead($: EngineInterface): Promise<boolean> {
272+ const [simple, attachmentsOff] = await Promise.all([
273+ $.env.get('CLAUDE_CODE_SIMPLE'),
274+ $.env.get('CLAUDE_CODE_DISABLE_ATTACHMENTS'),
275+ ])
276+
277+ return (
278+ !Switches.isSwitchedOn(simple) && !Switches.isSwitchedOn(attachmentsOff)
279+ )
280+}
281+
282+/**
251283 * The home directory a `~` in a Read's path stands for, read the way the
252284 * Read tool reads it.
253285 *
@@ -1,0 +1,3 @@
1+export * from './is-switched-on.js'
2+
3+export * as default from '.'
@@ -1,0 +1,14 @@
1+/**
2+ * The spellings an environment switch is on with, as the engine reads its own.
3+ */
4+const ON_SPELLINGS: ReadonlySet<string> = new Set(['1', 'true', 'yes', 'on'])
5+
6+/**
7+ * Whether an environment variable's value turns a switch on, read the way the
8+ * engine reads its own switches: `1`, `true`, `yes` or `on`, any case, trimmed.
9+ *
10+ * @param value the variable's value, or undefined when it is unset
11+ * @returns whether the switch is on
12+ */
13+export const isSwitchedOn = (value: string | undefined): boolean =>
14+ value !== undefined && ON_SPELLINGS.has(value.trim().toLowerCase())