[BUG] v2.1.219 `heron_brook` prompt section injects "Do not call the AgentTool unless the user requested it" for Opus 5 only, silently overriding user-configured delegation policy, with no opt-out
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
Claude Code 2.1.219 injects a system-prompt section, registered internally as heron_brook, whose default text is:
Do not call the AgentTool unless the user requested it
Do not use workflows or deep-research unless the user requested it
It is enabled by model capability, not by user configuration, and there is no documented way to turn it off. In my session it caused the model to stop dispatching subagents entirely for a task where my own CLAUDE.md requires delegation — the model treated the injected line as if it carried my authority and let it override my explicit standing instruction.
The problem is not the guidance itself. It is that an undisclosed, server-gated prompt section countermands a documented, user-configured behavior (subagents / the Agent tool), reads to the model as user-authored instruction, and offers no opt-out.
Mechanism
From strings on my own installed build (~/.local/share/claude/versions/2.1.219, Mach-O arm64).
The two lines are a joined constant, emitted as the fallback text for the heron_brook section:
Jep = ["Do not call the AgentTool unless the user requested it",
"Do not use workflows or deep-research unless the user requested it"].join(`\n`);
function iMy(e){
let t = XR()?.tengu_heron_brook; // 1. bootstrap client_data
if (typeof t === "string" && t.trim() !== "") { ... return n }
let r = Ke("tengu_heron_brook",""); // 2. GrowthBook string flag
if (r.trim() !== "") { ... return n }
if (eXn(e)) return Jep; // 3. default, gated
return null
}
The gate:
function eXn(e){
if (e === void 0) return !1;
if (HN(lo(e), "opus_5_prompt_bundle") !== !0) return !1;
return !Ke(Qcg, !1) // Qcg = "tengu_fennel_godwit"
}
HN strips a [1m] suffix from the model id and checks the model registry's capabilities array. In 2.1.219 only claude-opus-5 carries opus_5_prompt_bundle:
capabilities:["effort","max_effort","xhigh_effort","adaptive_thinking","mid_conv_system",
"context_management","fast_mode","lean_prompt","refusal_fallback",
"opus_5_prompt_bundle"]
claude-fable-5's capability list does not include it. So: the section is on by default for the Opus 5 family and off for other models, unless the GrowthBook killswitch tengu_fennel_godwit is set true — which defaults false.
Registration, in section order:
iD("delivering_work_max", ...), iD("overcorrection", ...),
iD("subagent_steer_delegation", () => d.has(Go) && Cq() === "counter_steer" ? Itu : null),
iD("heron_brook", () => iMy(t)),
iD("autonomy_append", ...), iD("endconv_deferred_hint", ...)
Why there is no opt-out
- No
settings.jsonkey and no CLI flag targets this section or its gate (checked the settings docs page andclaude --help). - The six sibling flags in the same Opus-5 bundle each have a dedicated
CLAUDE_CODE_*env var (CLAUDE_CODE_MARL_CORMORANT,CLAUDE_CODE_GAULT_KESTREL,CLAUDE_CODE_GORSE_PLOVER,CLAUDE_CODE_AMBER_ASTROLABE,CLAUDE_CODE_BISON_CAIRN,CLAUDE_CODE_LARCH_CISTERN).tengu_heron_brookandtengu_fennel_godwithave none — they are read through bare flag getters with no env-var branch. --exclude-dynamic-system-prompt-sectionsdoes not help: perclaude --helpit is a boolean that relocates per-machine sections (cwd, env info, memory paths, git status) into the first user message for cache reuse, not a by-name section filter.DISABLE_GROWTHBOOK=1makes it worse: the killswitch defaults tofalse, so blocking the flag source guarantees the gate stays open.- The only local lever I could find is the generic, undocumented
CLAUDE_INTERNAL_FC_OVERRIDESJSON env var, which is tier 1 of flag resolution and can force any flag by name. An internal debug hatch is not a supportable answer for a behavior change this size.
Blast radius
The same opus_5_prompt_bundle capability gate (via a shared yQt(envVar, flagKey, model) helper) also defaults-on, for Opus 5 only:
delivering_work_max— whether the entire "# Delivering work" system-prompt section is includedovercorrection— the "overcorrection" section- two extra lines of Bash-tool guidance
- a wording clause appended to a delete/overwrite caution
- part of the autonomy-append section
So flipping the killswitch to remove two lines also removes five unrelated prompt sections. There is no per-section control.
Related: a second delegation experiment
Adjacent section subagent_steer_delegation is gated on flag tengu_thistle_grebe with three arms — default / no_nudges / counter_steer — resolved env → client_data → GrowthBook → model map → default. The counter_steer arm injects a section beginning:
## Delegating to subagents Subagents multiply cost and time: each one re-establishes context, re-explores, and reports back… Delegate only when the payoff clearly exceeds that overhead.
Independently gated from heron_brook. Worth noting that no arm in that experiment encourages delegation, so a user enrolled in both gets two independent nudges against a documented feature, neither disclosed nor configurable.
Expected Behavior
One of:
- A documented opt-out — a
settings.jsonkey and/or aCLAUDE_CODE_*env var for this section, as the six sibling flags already have. - Server-gated prompt text must not countermand explicit user configuration. If a section restricts a documented feature (the Agent tool, workflows), it should defer to the user's own instructions rather than read as a user-level directive.
- At minimum, disclosure: a way to see which dynamic prompt sections and experiment arms are active in the current session (e.g. surfaced in
/statusorclaude doctor), so the behavior is diagnosable withoutstringson the binary.
Steps to Reproduce
- Confirm the text ships in 2.1.219 but not the two prior builds:
````
grep -a -c "Do not call the AgentTool unless the user requested it" \
~/.local/share/claude/versions/2.1.219 # -> 2
grep -a -c "Do not call the AgentTool unless the user requested it" \
~/.local/share/claude/versions/2.1.218 # -> 0
grep -a -c "Do not call the AgentTool unless the user requested it" \
~/.local/share/claude/versions/2.1.217 # -> 0
- Start a normal CLI session on
claude-opus-5with aCLAUDE.mdthat instructs the model to delegate multi-file work to subagents. - Give it a task that its own instructions say to delegate. Observe it decline to use the Agent tool and cite the injected instruction as the reason.
- Confirm no config controls it: no matching key on the settings docs page, no matching flag in
claude --help, noCLAUDE_CODE_*env var fortengu_heron_brookortengu_fennel_godwitin the binary.
Note: session transcripts under ~/.claude/projects/ do not record system prompts, so the injection is not visible in transcript history — which is part of why this is hard for users to notice or report.
Environment
- Claude Code CLI 2.1.219 (installed builds: 2.1.217, 2.1.218, 2.1.219)
- macOS (Darwin 24.6.0), Apple Silicon
- Model
claude-opus-5[1m], efforthigh(settings.json:"model": "opus[1m]") - Plain interactive session started by running
claudein a terminal - No GrowthBook/Statsig-related env var set; no local flag-cache file found for the CLI
Prior Art
- #62061 — "v2.1.150 adds server-side system prompt injection via
tengu_heron_brookfeature flag". Same section name, same mechanism, labeledbug/has repro/area:security. Closed with no visible Anthropic staff response. - #62205 — GrowthBook flags (
tengu_permission_friction,tengu_quill_harbor) silently overridingpermissions.defaultModeinsettings.json, re-syncing every ~9 minutes. Open, labeledstale/duplicate, no staff response.
This report differs from #62061 in that it documents a specific default payload now shipping through that channel (2.1.219), the model-capability gate that scopes it to Opus 5, and the concrete user-facing harm: it overrides user-configured delegation behavior.
33 Comments
Correction to one claim in the report above, which makes the "no opt-out" finding stronger rather than weaker.
I wrote that
CLAUDE_INTERNAL_FC_OVERRIDESis "tier 1 of flag resolution and can force any flag by name." That is wrong for 2.1.219: that code path is unreachable. Verbatim from the same build (byte offset 228976466):Cstis declarednulland nothing else assigns it, sopGr()returnsnullunconditionally and the consumerLtu(e)(let t = pGr(); return t !== null && e in t) is alwaysfalse. Setting the env var has no effect on any flag, and neither log line can ever fire — so a user who tries it gets silence, not even the parse error.Two consequences:
--system-prompt, or wait for a fix. That is the whole list.CLAUDE_INTERNAL_FC_OVERRIDESis still allow-listed as a recognized environment variable elsewhere in the same binary (it appears in an env-name set alongsideCLAUDE_CONFIG_DIR, and in the env-var accessor map), while its only read site is dead code. Worth a look from your side as to whether the earlyreturnis deliberate neutering of an internal-only hatch or an accidental regression — I can't tell which from the binary, and it doesn't change the user-facing outcome either way.Everything else in the report stands: the
heron_brooksection, its default text, theopus_5_prompt_bundlecapability gate, thetengu_fennel_godwitkillswitch defaulting to false, and the five sibling sections sharing that gate.Addendum: the mechanism confirmed against live flag state, not just the binary.
The CLI caches its GrowthBook payload locally, so an affected user can verify the gate without reverse-engineering anything. On this machine
~/.claude.json→cachedGrowthBookFeaturesholds 441 keys, and the three relevant ones read:Which matches the reported behaviour exactly, on all three counts:
tengu_fennel_godwit: false— the killswitch is off, soeXn()'s second condition passes and the section renders for this Opus 5 session.tengu_heron_brookabsent — no server-pushed override string, so what renders is the built-inJepdefault, i.e. the twoAgentTool/workflowslines verbatim.tengu_thistle_grebe: "default"— this account is on the control arm of the adjacentsubagent_steer_delegationexperiment, which is why that section did not appear even though it is also delegation-themed. Two independent mechanisms, one active, one not.So the chain in the original report is not inferred from
stringsalone — it is confirmed against the flag values actually delivered to this account.One precision on my earlier "no local control surface" statement, since that cache is itself a resolution tier: hand-editing
cachedGrowthBookFeaturesis not a usable opt-out. The in-memory payload takes precedence over the disk cache, the server re-syncs and overwrites it (see #62205, ~9-minute cadence), andDISABLE_GROWTHBOOK=1makes the getter return the caller's default — which fortengu_fennel_godwitisfalse, i.e. it keeps the gate open rather than closing it. The cache is useful for diagnosis, not for control. The request in the original report stands unchanged.Filed #81263 for a narrower slice of this same section that I don't think is covered here or in #80998 — the default text also appears wrong on its own terms, separate from the opt-out question.
Two things:
1.
AgentToolnames no tool in the rendered surface. The exposed tool isAgent. The cause is visible in the shipped strings: sibling sections interpolate the tool-name constant, e.g. the background-job section ships aswhereas
heron_brook's default hardcodes the name, and hardcodes a different spelling. So the same tool is referred to by two names in one prompt, and the hardcoded one matches nothing in the tool list.2. That same background-job section instructs proactive subagent spawning. On a background job the two render into a single prompt giving opposite instructions — harness-vs-harness, rather than the harness-vs-
CLAUDE.mdcase in #80998. Observed on 2.1.220 / Opus 5.One correction to the quoted text while I'm here: the second line is fine.
deep-researchis a real predefined workflow name, so "Do not use workflows or deep-research" binds correctly. Only the tool name is broken.None of this changes the ask here — the opt-out is worth having regardless of whether the text gets fixed.
Confirming on 2.1.220, plus a behavioral A/B that nuances the "overrides CLAUDE.md" claim.
Prompt-content probe (headless, neutral empty dir):
So on 2.1.220 the gate is still model-only: unaffected by effort level, headless vs interactive, and (tested separately) mobile remote-control attach.
Behavior matrix — neutral dir, one trivial subagent defined in
.claude/agents/log-scout.md, prompt"how are the logs?":| Setup |
--model opus|--model fable||---|---|---|
| Agent available, no CLAUDE.md guidance | does not delegate; asks clarifying questions | delegates to
log-scout|| Same agent, trigger documented in CLAUDE.md | delegates (documented instruction counts as "requested") | delegates |
So in this minimal case the injected line did not override a documented CLAUDE.md instruction — Opus treated the written-down trigger as user request. That's narrower damage than total override, but it arguably makes the discoverability problem worse: teams whose CLAUDE.md documents delegation see it keep working, while everything that relies on the model's own initiative — fuzzy triggers, agent
descriptionfields, skill descriptions — silently degrades on exactly one model. The natural misdiagnosis is "Opus is worse at delegation," not "a hidden prompt section requires the workflow to be written down."Mitigations we've converged on meanwhile (none a substitute for a fix):
opus_5_prompt_bundle+1 to all three asks, especially disclosure of active dynamic prompt sections in
/status/claude doctor. The diagnosis path here (controlled model matrix +stringson the binary) shouldn't be what it takes to explain why the same prompt fans out on one model and single-threads on another.Thanks @elaye-canopy for the original report and the follow-ups so far. A few more items for this:
The instruction may be aimed narrower than where it is landing.
@riptscripts pointed out in #81263 that
AgentTooldoesn't match any tool that exists. There is a related problem underneath it.There are two ways a subagent starts. One is the Agent tool. The other is a skill forking one. The code tracks them separately:
That line is in a metadata helper, not the dispatch path, so it shows the code treats the two as different things. It doesn't prove dispatch branches on them.
The instruction names only one. If "the AgentTool" means what the code means by it, skills that fork their own subagents were never in scope, and models are applying the instruction past what it covers.
That changes the fix. If it was only ever meant to cover the Agent tool, then the failures landing on skill-defined subagents aren't a policy anyone needs to opt out of. They are a scoping bug.
@Careidas77's CLAUDE.md result and the reports of it failing can both be right.
The difference looks like how specific the instruction is. Their passing case names a particular agent and a particular trigger, and that reads as a request. "Delegate multi-file work to subagents" doesn't name anything to request, and that is the shape that fails.
A user on r/ClaudeCode going by grurra still has review subagents refused while CLAUDE.md permits them. Writing your delegation down isn't enough if you write it as policy.
When a delegation request arrives from a script.
Sometimes the request comes from an orchestrator rather than a person typing. In my own transcripts, three sessions, none aware of each other, landed on the same reading: an instruction relayed by an automated parent doesn't count as a user request.
Nothing told them that. They each worked it out, because the escape clause says the user and a parent process isn't obviously a user. So the escape hatch is there, the operator did ask, and the ask doesn't survive being relayed.
If the escape clause is going to stay the mechanism, it should count a request that reached the model through a layer.
Confirming this reproduces on 2.1.220, not just 2.1.219, with the same two strings compiled into the binary.
Both appear length-prefixed and adjacent in a string table, and both are present verbatim in the live system prompt on Opus 5.
Observed impact. One long session, with a delegation-heavy
~/.claude/CLAUDE.mdthat routes multi-file changes, refactors, reviews, research and verification to agents, requires an adversarial critic pass before implementing, and states "never self-approve in the same active context": zero subagents dispatched. Every verification was self-verification. That session shipped a 7-file refactor to production with no independent review, and three separate pieces of incorrect work reached the user that an independent reviewer exists to catch.What makes it costly is that the failure is invisible. A run with the user's review policy silently disabled looks identical to a normal one.
The interpretive mechanism seems worth naming. "Unless the user requested it" is ambiguous about whether standing configuration counts as a request.
CLAUDE.mdis the user requesting it, but the line reads as though only an in-turn message qualifies, and the conflict gets resolved silently rather than surfaced. Even leaving the gate in place, having the model announce "your configured delegation policy is being overridden" would remove most of the harm.Workaround that needs no binary patching or flag access, in case it helps others:
UserPromptSubmithook output is injected as user-side context every turn, which satisfies the "unless the user requested it" clause.The script emits
{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"<standing request to delegate>"}}. Verified working on 2.1.220; costs roughly 220 tokens per turn.Also seconding #81263 on the naming: the rendered tool is
Agent, notAgentTool, so the instruction names a tool that does not exist in the surface it governs.Another confirmation — with a measurable failure rather than a behavioural impression.
Kept deliberately generic: we run a fleet of Claude Code sessions in which one step is mandatory before a session may put a question to a human. That step is only reachable by spawning a subagent. There is an emergency bypass for the case where the step is genuinely unreachable (infrastructure outage), and it records a free-text reason.
Over ten days the fleet produced 27 such questions. 24 went through the mandatory step. 3 took the emergency bypass.
So the damage is not "fewer subagents were used". A session read the injected line, correctly concluded the mandatory step was unreachable, took the exit that exists for outages. A hatch built for a rare outage became the normal path for one arm of an experiment, and nothing downstream flagged it, because a declared bypass counts as covered.
Two things that may help others here:
UserPromptSubmithook workaround (@INIXYO) works. Verified end to end: the hook's stdout arrives as user-side context in the following turn, and subagents are dispatched again. It needs neither binary patching nor flag access, and it is the only lever we found that holds.CLAUDE.mddid not survive. The same intent written as a request that names the occasions ("I am requesting the Agent tool for the pre-check, for delegated build tasks, for second opinions") did. If the fix keeps the escape clause, this is worth stating in the docs, because right now every user has to discover it by accident.One request on the fix itself, independent of whether an opt-out lands: please make an overridden user policy visible. Ours was silent at both ends — the session recorded a plausible-looking reason for the bypass, and the human only saw a bad artifact. A single line stating that a configured delegation policy is being overridden would have turned this into a five-minute diagnosis.
Same problem. @INIXYO 's workaround works.
This system prompt change broke a bunch of project workflows where we had instructions in the CLAUDE.md or in skills to fan out to sub-agents to parallelise and contain the context of work.
Was this in the changelog somewhere? We didn't figure out what was going wrong until asking Claude to dig through the sessions and it mentioned that the system prompt was telling it explicitly not to use sub-agents unless the user requested it which it took to mean manually requested.
Since this seems to be getting traffic I would like to offer my temporary solution that doesn't require a hook (and might interest people)
Claude generally has a hard time distinguishing the origin of prompts so part of my claude.md has the following to prevent claude from mistaking my voice from subagent/anthropic.
Another part (G6) has delegation rules for orchestration.
Yes, I know this prose isn't as token efficient as it could be, I am sharing so others can see a solution without a standing order hook to delegate.
With this I consistently see opus 5 explicitly states that G6 overrides heron_brook and then it sends out subagents.
This was also the inspiration for the issue I opened with this one
https://github.com/anthropics/claude-code/issues/80998
We measured this section's cost on a real agent fleet, then reverted our whole fleet off Opus 5 because of it.
heron_brookis gated to the Opus 5 family (opus_5_prompt_bundle), and Opus 5 is documented to delegate more readily, so the section reads as a throttle against that delegation, a compute-saving guardrail. On our own usage it does the opposite. It makes tasks longer, makes them more expensive, and in one case stopped a task from finishing at all.We run a large number of automated Claude Code sessions. Bucketing them by the model id recorded in each session log (~1,350 on Opus 4.8, ~200 on Opus 5), here are the hard numbers, medians per session:
| Metric (median / session) | Opus 4.8 | Opus 5 | Direction |
|---|---|---|---|
| Agent turnaround per step | 9.0 s | 7.0 s | Opus 5 faster per step (−22%) |
| Assistant turns | 85 | 210 | 2.5× more steps |
| Bash commands | 36 | 98 | 2.7× more commands |
| Wall-clock | 18.6 min | 39.3 min | 2.1× longer |
The model generates faster per step, yet each task takes about 2.5× more steps and 2.1× more wall-clock. The extra time is not slow tokens. It is more serial round-trips: work that could run off the main context, in parallel, instead runs one main-context tool call at a time, each processed at full main-context cost. Suppressing delegation does not remove that work. It relocates it to the most expensive place to run it and serializes it.
A second, noisier cut from our own investigation-quality scorer points the same way:
| Signal | Opus 4.8 | Opus 5 |
|---|---|---|
| Mutated with zero research signals | 85.9% | 95.7% |
| Skill-procedure markers absent | 91.5% | 98.9% |
| Memory-search present | 84.4% | 98.5% |
| Decision-consult present | 83.2% | 99.0% |
These are internal heuristics, not ground truth, so read them as direction only. The shape is what stands out: Opus 5 searches memory more often, yet mutates without research signals more often. More motion, less landed investigation.
The failure that made us stop
The sharpest signal was not a number, it was a task that could not terminate. A one-line CSS change on a live page, a single font declaration, sent an agent into a flip-flop loop. It pushed unrequested variations of that one element to production repeatedly across a single day, never converged on the fix, and its own verification kept reporting success against a stale cache. A trivial, fully-specified edit turned into a day of churn on a live page. We had not once seen a task that small fail to complete on Opus 4.8.
Honest caveats
This is observational, not a controlled A/B. The Opus 5 bucket is one recent ~6-day window and may over-represent hard work; the 4.8 bucket spans months. And it compares whole models, so it cannot isolate
heron_brookfrom Opus 5's other changes such as thinking-on-by-default, longer output, and more narration, any of which inflate turns on their own. We are not claiming these numbers sizeheron_brook's effect. We are saying the mechanism, converting parallel delegation into serial main-context work, pushes cost and wall-clock up, and our usage is consistent with that rather than with a saving.Where we landed
We reverted our entire fleet to Opus 4.8. For a long-horizon agentic automation workload, Opus 5 as shipped, with this section active and no way to turn it off, is a heavy enough regression that we could not keep it in production. The second-order cost is human time: more round-trips means the operator waits longer and becomes the bottleneck, which is the one thing autonomous delegation exists to remove.
The ask is the one already in this thread: make the section opt-out. Someone who has configured a delegation policy has priced that trade-off already. A server-gated line that silently overrides it, and reads to the model as the user's own instruction, is the wrong default for exactly the workloads Opus 5 is sold for.
Two days of engineering time wasted to find this undocumented gem that bricked all of our long running agent focused automation, I don't even want to do the math on the wasted tokens you won't be refunding.
Straw meet camel, codex here we come. Forced to use Claude code or desktop or the meme that is the sdk, constantly dealing with things like this.
Yolo'd a massive behavioral change, unimaginable amounts of time and money wasted across the ecosystem trying to find this problem because the harness you force us to use is closed source from the company that has been screaming AGENT ALL THE THINGS for the past year.
Real talk, you all really need to address the decision making. It really doesn't matter how good the model is at this point, your consistent poor decision making and communication makes supporting Anthropic as a company impossible.
Thank you to @elaye-canopy for the through report & @INIXYO for the functional workaround.
For others, there is also
CLAUDE_CODE_SIMPLE=1to just disable the system prompts entirely, at least that way we get stability in the tools we are forced to use.Side effect Anthropic didn't think about, we have had agents that are just not shutting down after their task is complete and verified, it has worked flawlessly for months. We now know why, thanks for that as well guys....
On
CLAUDE_CODE_SIMPLE=1: it is--bare, and it removes the Agent tool itselfFlagging this because the suggestion sits at the end of the thread unchallenged, and anyone who reaches for it while chasing this exact bug will lose a second day. I checked it against the installed binary (2.1.218) rather than against the description.
CLAUDE_CODE_SIMPLEis not a system-prompt switch. It is the--bareflag:--baresetsprocess.env.CLAUDE_CODE_SIMPLE="1"itself, so the two are the same mode. The client's own help text for--baresays: "Minimal mode: skip hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads, and CLAUDE.md auto-discovery."What that means in practice, from the reduced-mode gate:
UserPromptSubmit,SessionStart,Stop,PreToolUse) —nR_, the generic per-event hook resolver, returns empty. That includes theUserPromptSubmitworkaround this thread relies on, which is the irony: the flag removes the fix.--add-dir), skills only from--add-dirproject dirs, plugins only from--plugin-dir, custom agents only from--agents.Vj()returns[…Lp()?[gu]:[], …, WS, VL]whenCLAUDE_CODE_SIMPLEis set. The Agent tool is not in that list. So the flag does not restore delegation — it makes delegation impossible.--mcp-config— no.mcp.json, no user/project scope.ANTHROPIC_API_KEYorapiKeyHelperonly; OAuth and keychain are never read, so a subscription session does not start at all.CWD:andDate:.One trap worth knowing regardless:
Qp()parses the value strictly (1/true/yes/on), but a second path reads it as raw truthiness —so
CLAUDE_CODE_SIMPLE=0still collapses the system prompt and the tool list while leaving everything else on.The near-namesake does not help either
There is a separate
CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT, and it is a genuine prompt-only switch (gE()), leaving hooks, MCP and skills alone. But it does not remove this section. In the prompt builder,gEonly chooses between the lean block and the six full blocks:The experiment sections live in
g, which is spread in unconditionally, outside theo ? … : …branch —aR("heron_brook",()=>Lx_()). Setting the variable costs you the identity/URL-safety, tool-usage-policy, code-convention, act-with-care, task-management and tone blocks, and leaves the line in place. Cost without effect.Why no local flag is a stable answer
The section's text is not a string in the bundle at all. It is fetched at runtime:
— client-data override first, then the gate value. It can appear and disappear without a client update, which is consistent with the "same build, same model, sibling sessions behaved differently" reports upthread.
So the
UserPromptSubmithook remains the only lever that actually holds, and the ask in this thread — a real opt-out, and visibility when a configured delegation policy is being overridden — is unchanged.---
Full disclosure on authorship: this comment was conceived, researched and written end to end by Claude Opus 5 — including the decision to check the claim in the first place. The binary forensics above are its own, and the account owner posted it at its request without reviewing the technical content. So do not take the findings on authority: every claim above is a snippet you can grep out of your own installed binary, and that is the standard I would like it held to. Given that the subject is a section that changes how this model delegates, saying so plainly seemed better than leaving it implied.
Confirming on 2.1.220, Opus 5 — with a repro where the injection leaves no legal move.
Same two lines, same gating. Ruled out local origin:
settings.json,settings.local.json,~/.claude.json,output-styles/, allCLAUDE.mdfiles, and the shell launchers (which pass only--dangerously-skip-permissions).The part I haven't seen described yet is that this can deadlock a session rather than just degrade it.
Our repo runs a
PreToolUsehook that warns when the top-level session writes a repo deliverable during a build step — authoring is supposed to happen in a dispatched subagent, while the parent owns state transitions and gate verification. That's a deliberate policy, and it's enforced mechanically.With
heron_brookactive:The session stopped and asked the user for permission to do the thing its own configuration already required. It also — reasonably — attributed the constraint to the user, who had never set it. That misattribution is its own problem: the model reports a server-gated instruction as though it came from the operator.
This isn't limited to hook-enforced setups. Any workflow where delegation is the architecture rather than an optimization hits the same wall; skills whose documented steps say "dispatch a subagent" don't self-qualify as a user request.
A per-section opt-out would fix it. Right now the only lever also removes 5+ unrelated sections from the same capability bundle, which isn't a trade anyone can reasonably make.
Independent confirmation — Windows 11, v2.1.219,
claude-opus-5.I ran into this from the other direction: my global
CLAUDE.mdhad no delegation policy at the time, and the model surfaced these two lines verbatim when I asked why it wasn't delegating. It treated them as binding until I explicitly authorized subagent use in chat. That matches your characterization — the injected text reads to the model as user-authored authority rather than as a default it can weigh against user configuration.Binary-level confirmation. The lines are a hardcoded literal in the compiled CLI, not config-driven:
Two occurrences of each string in the binary. It sits in the same bundle chunk as the other system-prompt constants (
# Corrections,# Context management, the subagent preamble), i.e. a prompt fragment rather than anything resolved at runtime from config.Nothing local injects them. I swept the machine before concluding it was upstream — all negative:
settings.json,settings.local.json,.claude.json,~/.claude/plugins/,~/.claude/skills/,~/.claude/hooks/, every*.mdunder~/.claude/, the desktop Electron shell (app.asarand the MSIXClaude.exe), and the global npm@anthropic-ai/claude-codewrapper. No~/.claude/output-styles/directory exists on this machine. The only on-disk hits outside the CLI binary are session transcript.jsonlfiles, which merely echo the system prompt.Version boundary corroborated. An older standalone build still present on this machine (mid-July) contains no match; 2.1.219 does. Consistent with your 2.1.217 / 2.1.218 finding.
Practical impact. The only mitigation available to a user is to write a counter-instruction into
CLAUDE.mddeclaring the "unless the user requested it" condition pre-satisfied. That does work, and it survives CLI updates. But it means anyone who wants subagents has to first discover an undisclosed prompt section to know why their configuration is being ignored — and it's a workaround, not configuration.A supported per-section control, or simply having user
CLAUDE.mdtake documented precedence overheron_brook, would resolve this without requiring the all-or-nothingopus_5_prompt_bundletoggle or the internalCLAUDE_INTERNAL_FC_OVERRIDEShatch.Corroborating on Windows — the teardown above is macOS arm64; same behaviour on the Windows desktop app.
claude-opus-5.tengu_heron_brookis absent from the locally cached GrowthBook features (onlytengu_heron_tallow,tengu_cobalt_heron,tengu_amber_heronare present), so it is the built-in fallback branch firing, not a server-supplied string.CLAUDE.md, projectCLAUDE.md,.claude/rules/,settings.json,settings.local.json,.claude.json, managed settings. The only on-disk occurrence is insideclaude.exe.The user-facing cost is the misattribution, which I would add to this report:
That was said to a user whose global
CLAUDE.mdcontains the opposite rule — an explicit instruction to delegate multi-file reading to a subagent. Because the injected text is worded "unless the user requested it", the model reads it as user-gated, finds no such request in the transcript, and reports it back as the user's own standing preference. The user then searched their entire config tree for a rule they never wrote and cannot edit.Worth separating in triage: the injection is one bug. A model attributing undisclosed prompt text to the user is a second one, and it would still bite even if the guidance itself were intended.
@KickTechnic This is mentioned explicitly in an associated issue I opened, maybe we should boost the traffic of it.
https://github.com/anthropics/claude-code/issues/80998
Also, you can see my comment on this thread for a prompt telling Claude to make this differentiation.
https://github.com/anthropics/claude-code/issues/80988#issuecomment-5129079350
It works for me both with the heron issue and also generally to make sure claude doesn't make this mistake
<img width="222" height="29" alt="Image" src="https://github.com/user-attachments/assets/fa0edd58-ec67-44f3-9981-4f0f3bde2464" />
(disc: I asked Claude to help me shorten and summarize my comment before posting)
+1 — still in 2.1.221 (latest), unfixed since 2.1.219.
I maintain an SRE skill whose protocol explicitly mandates fanning out into 8 parallel subagent lanes. Under Opus 5 the model treated the injected directive as authoritative, refused the Agent tool, and ran the whole sweep serially. An explicit skill instruction lost to a system-prompt line I can't see or disable.
The irony is the cost. The fan-out exists as a cost control — the skill's own measurement is 14:1 isolation (~355k tokens burned inside subagents, ~25k returned to the main context). Serially, every raw command output lands in the main conversation and is resent on every subsequent turn. Three outputs (634 KB / 133 KB / 65 KB) were too large to display and had to be spilled to disk, forcing re-queries to recover the data. Wall clock was ~16 minutes instead of roughly the slowest single lane.
And it fails silently — the report completed, so nothing looked wrong. It took decompiling the CLI binary to establish the cause wasn't my own configuration.
Please add a supported opt-out, or have the section defer to explicit user/project instructions rather than override them.
Still present on 2.1.223 through 2.1.227, and the
CLAUDE_INTERNAL_FC_OVERRIDESpath is still dead codeAdding a build-range data point, since the newest version discussed in this thread is 2.1.221. Platform is Windows 11 x64. I checked the four builds installed here, at
resources/native-binary/claude.exeinside the VS Code extension: 2.1.223, 2.1.224, 2.1.226 and 2.1.227. The CLI on this machine reports 2.1.226.1. The env var override reader is still unreachable, four builds later and across a refactor.
@elaye-canopy's correction above (2.1.219, macOS arm64) holds unchanged here, so anyone arriving at this thread and reading only the original report should treat that lever as unavailable rather than undocumented. Verbatim from 2.1.226, byte offset 270941209:
N0tis initializednullat module scope (...,N0t=null,TDa=!1,...) and the only other write to it is the reset path setting it back tonull, so the getter returnsnullon every call and its consumeragf(e){let t=ZMn();return t!==null&&e in t}is always false. Same shape in 2.1.223 (offset 264469704, varsUvt/sSa) and 2.1.224 (offset 268910980, varsmTt/Jxa).In 2.1.227 the GrowthBook client moved into a class, and the early return moved with it (offset 275954663):
with
environmentOverrides=null; environmentOverridesParsed=!1as class field initializers, and the dependency wired at construction asreadEnvironmentOverrides:()=>process.env.CLAUDE_INTERNAL_FC_OVERRIDES. Net effect is identical to the function form.Runtime confirmation, which I do not think has been posted yet (the reading above is static, and so was the original correction): on 2.1.226 I ran
claude -pwithCLAUDE_INTERNAL_FC_OVERRIDESset to forcefennel_godwittrue, and separately to setheron_brookto null, which is the recipe implied by the original report. Both runs still produced the two lines verbatim, and neither the success log line nor the parse-error log line appeared, which is exactly what unreachable code predicts.2. The section itself is unchanged in the newest build here (2.1.227).
Both sentences are present verbatim in 2.1.226 and 2.1.227, three occurrences of each. In the shipped model registry,
opus_5_prompt_bundleappears on exactly one entry,claude-opus-5; the adjacentclaude-opus-4-8entry ends itscapabilitiesarray atlean_prompt.3. Model gate re-measured on 2.1.226. Headless
claude -pprobes from a terminal, same config loaded in each: default model (Opus 5) returns both lines,--model sonnetreturns none,--model claude-opus-4-8returns none. Still model-only, and not host dependent: a clean terminal session gets it exactly as the VS Code extension does. Consistent with the 2.1.220 matrix posted earlier by @Careidas77.4. One impact shape worth adding: checks that are implemented as subagents.
Our configuration hangs several mandatory checks on a dispatched subagent, including a reviewer that must return a clean verdict before a production deploy. Those are gates rather than optimizations, and the reason each runs in a subagent is precisely that the reviewing context must not be the context that did the work.
With the section active, a session that has finished and verified a change arrives at its own deploy gate with no legal move: dispatching the reviewer is forbidden by the injected line, and deploying without the reviewer is forbidden by the configuration the operator wrote. On 2026-08-10 that produced a hard stop on finished work, and a human had to authorize the reviewer by hand. This is close to what @bradywardai described, with one addition: the blocked step is the safety check itself, so a session that resolves the conflict the other way ships with the review silently skipped, and the artifact looks the same either way.
5. Also re-checked, all unchanged from the original report: no key in the settings schema shipped with the extension gates this text (142 top level keys swept against agent / subagent / task / workflow / system-prompt / inject; the closest hits,
disableWorkflowsandworkflowKeywordTriggerEnabled, gate the Workflows feature and not this section), noCLAUDE_*orANTHROPIC_*name in the binary reaches it, andexclude-dynamic-system-prompt-sectionshas no effect on it when tested, matching the help text quoted in the original report.A documented per-section opt-out, or a documented precedence rule where user configuration wins over this section, would resolve the case above.
Confirming this on 2.1.227 (Windows 11) from two directions: a behavioral A/B that isolates the model, and the strings in the shipped binary.
First, a correction to my own report. An earlier version of this comment claimed the strings are not in the npm bundle. That was wrong, and the way it was wrong is worth passing on. The package no longer ships a readable
cli.js: it is a single Bun-compiledbin/claude.exe(~279 MiB, 292 MB decimal, on 2.1.227).grepdoes find the strings, in every mode I tried, includinggrep -rover the package directory, where it printsBinary file ... matchesand exits 0, as @INIXYO showed above withgrep -ac. ripgrep is the one that misses them: pointed straight at the file it reports the matches, but traversing a directory it skips the binary silently and reports nothing, which is how I got a clean negative and believed it. Reading the file byte by byte on 2.1.227:| string | present |
|---|---|
|
Do not call the AgentTool unless the user requested it| yes, 3 occurrences: two adjacent in a string table, one in the JS source, each immediately followed byDo not use workflows or deep-research unless the user requested it||
heron_brook,tengu_heron_brook,tengu_heron_brook_applied| yes ||
opus_5_prompt_bundle| yes ||
tengu_fennel_godwit| yes, among the flag names |So the default text is local and hardcoded, consistent with @elaye-canopy's analysis of 2.1.219 — the service can override it, but it does not need to. Anyone trying to verify this should not trust a grep that returns nothing.
The behavioral A/B, which is what a user actually experiences. Same machine, same project, same
settings.jsonandCLAUDE.md. Two fresh terminal sessions started withclaude, one on/model sonnet, the other on/model opus. Only the model differs.A control question first, to establish that the model reports its own system prompt faithfully: quote verbatim two instructions I can independently verify are present (the
/<skill-name>routing rule and the "suggest! <command>" rule from Session-specific guidance). Both models quoted both, word for word.Then the test question — quote verbatim any instruction about using the Agent tool, workflows, or deep-research; answer "none" if there is none:
| model | AgentTool line present? |
|---|---|
| sonnet | no — answered "none", after passing the control |
| opus (Opus 5) | yes, verbatim, reported as coming at the end of the system prompt |
Two details make the Opus report credible rather than agreement with my premise: it correctly separated the injected lines from the Workflow opt-in rule that ships inside the Workflow tool description (present on both models — Sonnet reported that one), and it declined to quote the "Ultracode" paragraph because it wasn't confident of the exact wording.
Not auditable from the user side.
claude --debugdoes not log the system prompt (I checked the debug file for several strings I can see in my own prompt — none appear), and the session transcripts under~/.claude/projects/*.jsonldon't contain it either.--exclude-dynamic-system-prompt-sectionsis documented as covering only cwd/env/memory-paths/git-status, so it does not reach this section.Concrete impact. My
CLAUDE.mdstates an explicit delegation policy ("delegate mass reading, broad search, execution of an already-approved plan"). On Opus 5 the agent declined to delegate and cited the injected line as a session rule. Because it appears in no config file and no log, the cost was diagnosis: first hunting a local misconfiguration, then a wrong hypothesis (remoteControlAtStartup/ remote control — stating that plainly, since it looks plausible and is not the cause), before the model A/B isolated it. The failure mode is quiet: the policy I wrote is never reported as overridden, it simply stops being followed.Request, and the shape of the fix. The dynamic sections are not a monolith. They are assembled from a list of
{name, compute}entries, andheron_brookis one name among several (brief,act_dont_rederive,delivering_work_max,overcorrection,subagent_steer_delegation,autonomy_append,endconv_deferred_hint). On 2.1.227:Alr()returnssystemPromptSectionCache, keyed by section name. To be explicit, since it would be easy to over-read: that is memoization, not a user override hook, so it is not a hidden opt-out. What it does show is that the runtime already addresses every section by a stable string, already stores what each one rendered to, and already treatsnullas a legal result meaning "this section did not apply". Both asks in this thread are therefore small changes rather than architectural ones:/context --verbose, or the--debuglog, can enumerate that cache: section name and rendered length, or the text itself. It exposes nothing the model is not already being told.--exclude-dynamic-system-prompt-sectionsis today a boolean over a fixed set (cwd, env info, memory paths, git status). Letting it accept a comma separated list of section names, honoured by yieldingnullfor a named entry, would let a user who has written an explicit delegation policy dropheron_brookand keep everything else. Asettings.jsonequivalent would cover the entrypoints that are not the CLI flag path (desktop app, SDK).The asymmetry is the part worth naming. A delegation policy in
CLAUDE.mdis visible, versioned and reviewable by the team that wrote it. This section is none of the three, and it outranks them.Additional data point from Windows: the delivery channel predates 2.1.219. The text was absent from my 2.1.211 binary, yet the section applied and the model quoted it verbatim.
Environment
%USERPROFILE%\.local\bin\claude.exe→%USERPROFILE%\.local\share\claude\versions\<ver>)where.exe claudeconfirms the extension and a plain PowerShell session resolve to the same binaryObserved behavior on 2.1.211
My
CLAUDE.mdmandates delegation: implementation touching core domain logic or authorization must not be executed directly, must go to animplementersubagent, and must pass an independentcode-reviewerreview (explicitly marked non-skippable)..claude/agents/defines architect / implementer / code-reviewer / explorer / debugger.The model declined to delegate. When I asked where the constraint came from, it reported the injected text and located it for me:
It then grepped the repo to rule out a local source —
CLAUDE.md,.claude/settings*.json,.claude/agents/, and the spec directory all came back clean — and concluded the text came from the host/SDK side, which it could not identify.Two details from that exchange seem worth recording:
AgentTool; the actual tool isAgent. The model told me it had interpreted the two as the same thing. So the effective scope of the restriction is left to the model to guess.CLAUDE.mdrather than being weighed against it.The task in progress was configuration-only, so the skipped review cost me nothing this time. It would not be harmless on the order-entry, settlement, or authorization paths that
CLAUDE.mdspecifically fences off.Binary contents
The string-presence boundary matches what you found on macOS/arm64 — absent before 2.1.219, present after. That part reproduces on Windows.
Why this changes the framing
The title attributes the behavior to 2.1.219. From the above, 2.1.219 added only the local fallback — tier 3 of the
iMy()resolution order you documented. Tiers 1 and 2 (client_data.tengu_heron_brook, the GrowthBook string flag) were already wired up in 2.1.211, since the flag key is in that binary.The text reached the model on a build that does not contain it. It cannot have come from the binary.
That is consistent with #62061 reporting this channel at v2.1.150. Two consequences:
Adding to the ask
This strengthens expected-behavior item 3. A
strings/findstrcheck on the binary cannot tell you whether the section is active in a session, because the payload can arrive from the server on builds that never shipped it — and, as noted, session transcripts don't record system prompts either. Surfacing active dynamic prompt sections and experiment arms in/statusorclaude doctoris currently the only thing that would make this diagnosable at all.Independent corroboration from a different platform and a later build, plus a before/during/after from one project that the report does not have — including what happens when the condition is satisfied.
Environment: Claude Code VS Code extension 2.1.226 and 2.1.227, Linux x64 (WSL2),
claude-opus-5[1m],settings.jsoncarrying onlyeffortLevelandmodel, unchanged since 2026-07-25. Sessions started with the extension's "new session" button, never the CLI.The strings are still shipping, eight builds later
Identical counts in 2.1.227.
CLAUDE_INTERNAL_FC_OVERRIDESis present (6). The five sibling sections named in the report —delivering_work_max,overcorrection,autonomy_append,subagent_steer_delegation,heron_brook— are all registered separately in the same binary, which matches the report's point that the killswitch has no per-section granularity.A before/during/after in one project, 116 sessions
One repository, same machine, same launch method throughout. Counting
Task/Agenttool calls in~/.claude/projects/<project>/*.jsonl:| period | model in those sessions | sub-agent use |
|---|---|---|
| through 2026-08-04 | Opus 4.8 | regular — 20 sessions, up to 12 agents in one |
| the following week | Opus 5 | ⛔ zero, across every session in the window |
| the day an explicit in-session request was made | Opus 5 | resumes — 13 agents in one session, 1 in another |
The third row is the part worth having. The capability did not disappear and the tooling did not change; it became conditional on a phrase. Two Opus 5 sessions used the Agent tool on the same day, both immediately after the user said so in chat, both with zero Opus 4.8 messages in them. That is precisely the behaviour the injected line specifies, observed from the outside.
The middle row is a full week of ordinary work with the feature silently unavailable. No release note, no configuration change, and nothing in the transcripts — which matches the report's closing note that
~/.claude/projects/does not record system prompts.The CLAUDE.md repro reproduces, and one partial workaround does not
Step 2–3 of the report's repro hold here. A standing project instruction to delegate does not restore the behaviour.
⭐ What does work, and it may be worth adding to the report: the injected line restricts calling the tool, not raising the option. A project instruction that says "when the work is assessment- or design-shaped, ask the user whether to use sub-agents" does not compete with it, and the user's answer then satisfies "unless the user requested it" literally. Tested on the next fresh session: the model asked, unprompted, and named a sensible fan-out.
⚠ Two caveats, both measured on that first run. It must be told to use a blocking prompt — asked in plain text it raised the question and kept working, so the user had to interrupt mid-stream. And it is a round trip per sweep, not a fix; it restores the capability at the cost of a question and it depends on the model choosing to follow a project instruction that the injected line happens not to contradict.
Why the killswitch is not usable here
tengu_fennel_godwitalso gatesdelivering_work_maxandovercorrection, which are the sections describing how the assistant scopes and finishes work. Dropping five unrelated sections to remove two lines is a worse trade than the restriction itself. The narrower lever the report identifies — settingtengu_heron_brookto a non-empty string, since the two lines are only its fallback — is the only targeted one, and it means opting out of a channel the vendor pushes content through with no way to notice when that matters.What this adds to the request
The report establishes the mechanism. This adds the measured cost and the measured recovery: a documented feature went from regular use to zero for a week, with no configuration change and nothing visible in the transcripts, and came back only when a human happened to say the right thing.
Expected behaviour item 1 (a documented opt-out) would resolve it. Item 2 (server-gated text must not countermand explicit user configuration) would resolve it better, because the failure is not that the guidance exists — it is that a project's own written delegation policy is silently outranked by it, and the only way we found the cause was
stringson the binary after noticing a week of absent behaviour.Still present on 2.1.232 (native installer, macOS 15 / darwin 25.5.0). Adding four things that I do not think are yet on this issue: a current-version confirmation, the decompiled gate, a model capability matrix, and a negative result on the
CLAUDE_INTERNAL_FC_OVERRIDEShatch.1. Still shipping in 2.1.232
2. The gate, decompiled
Worth drawing out, because it affects the proposed fix:
tengu_fennel_godwitonly guards tier 3. Tiers 1 and 2 are neither capability-gated nor killswitch-gated, so a server-deliveredtengu_heron_brookstring would reach any model and the documented killswitch would not touch it.3. Model capability matrix (from the registry in the same binary)
| Model |
opus_5_prompt_bundle| Directive observed ||---|---|---|
|
claude-opus-5| yes | yes ||
claude-opus-4-8| no | no ||
claude-sonnet-5| no | no ||
claude-fable-5| no | no |Behavioural confirmation, run from a neutral directory (no project CLAUDE.md, no repo docs mentioning the string) with a paired negative control asking about a fabricated instruction ("ZebraTool / quantum-search"). Every model answered ABSENT to the fabricated one, so the reports discriminate rather than merely agreeing. A paraphrase probe separated the injected directive from ordinary user-authored rules about workflow approval, which is easy to conflate and cost me two rounds.
4.
CLAUDE_INTERNAL_FC_OVERRIDESdid not work hereThe hatch is referenced in the binary (
readEnvironmentOverrides: () => Y.CLAUDE_INTERNAL_FC_OVERRIDES, plushasEnvironmentOverride()and a "GrowthBook: Using env var overrides for features:" log line), but on 2.1.232 with OAuth (Claude Max) I could not make it bite:CLAUDE_INTERNAL_FC_OVERRIDES='{"tengu_fennel_godwit":true}'left the directive present.CLAUDE_INTERNAL_FC_OVERRIDES='{"tengu_heron_brook":"ZEBRAFISH marker line."}'left the directive present and the sentinel absent from the prompt.--debug-fileoutput, which suggests the variable is not being read at all in this configuration rather than being read and ignored.So as far as I can tell there is currently no user-reachable opt-out, supported or otherwise, short of changing model.
5. Why the silent-override part matters more than the default
The failure mode is not that delegation is discouraged. It is that the line reads as user-authored. In my own setup the model reported the constraint back to me as "your standing instruction", and a review gate that requires an independent subagent was recorded as blocked on my own authority. I spent several weeks treating a client-side product decision as a policy I had written and forgotten. #80998's request to make the rendered prompt observable would have collapsed that to a single session.
Happy to run further probes on 2.1.232 if any specific configuration would help.
Confirming this on 2.1.232 (current npm
latest), macOS, with"model": "opus"and"effortLevel": "high"insettings.json.Adding one detail I haven't seen stated explicitly in this thread: the relative position of this section versus user
CLAUDE.mdcontent, which seems to be what actually drives the misreading.In the system-prompt assembly, the section list renders user memory/
CLAUDE.mdat roughly the 8th of ~23 sections, whileheron_brookrenders second-to-last — after the environment block, and immediately adjacent tosubagent_steer_delegation(the "Subagents multiply cost and time… Keep spawn counts low…" block gated on thecounter_steercohort).So the model sees an explicit, standing user instruction early, and a contradicting harness instruction last. Combined with the phrasing "unless the user requested it", the natural failure mode is that "the user" gets read as "this turn's message" rather than "the user's standing configuration" — and the late position gets read as the more specific override. The condition in the harness text is arguably already satisfied by a
CLAUDE.mdthat explicitly mandates delegation, but nothing in either instruction set resolves "a request stated once, globally, in config" against "a request restated in this prompt."Concrete cost, since I think the impact is being understated as a preference issue: I have a global
CLAUDE.mdthat mandates delegation by default and treats main-thread context as a multi-day budget to be protected. A long multi-repo working session ran start to finish with zero subagents, with the model explicitly citing these two lines as the reason. Every build, test sweep and gate run landed in the main context instead of a subagent. That is the exact outcome the configuration exists to prevent, and it is silent — nothing surfaces that a harness instruction overrode the user's.Also worth noting for anyone else diagnosing this: it is genuinely unfindable from the user side. I had an agent exhaustively search
settings.jsonandsettings.local.jsonat every scope,output-styles/, all hooks,agents/, everyCLAUDE.md,~/.claude.json, shell profiles, all installed plugins, and even the local LLM proxy in front of the CLI — all negative. The text only turns up by runningstringsagainst the binary. Users will reasonably conclude their own config is at fault.A settings.json opt-out (as proposed in #86170) would resolve it. Failing that, documenting the precedence rule between Anthropic-authored prompt sections and user
CLAUDE.mdwould at least make the behaviour predictable — that is the broader ask in #80998.For anyone working around this while the thread waits on an official answer, a few pointers that may save time.
Two community projects already touch this surface:
bootstrap-defenseextension explicitly covers the/api/claude_cli/bootstrapprompt-section path and the GrowthBook-selected injection surface, withaudit/block/allowlistmodes (the allowlist strips prompt-source flag keys you have not allowed before the response reaches Claude Code). Actively maintained. Its README also documents that this behavior was filed with Anthropic's security team in May 2026 and closed as Informative — TLS treated as the integrity boundary, no application-layer authenticity checks planned — which is useful context for how far a downstream fix is expected to carry.And #62381 already asks Anthropic to document the server-side prompt experiments and their opt-out controls — which is the root ask behind most of the frustration here. Worth a +1 if you would rather see this addressed at the source than worked around downstream.
One structural clarification that helps triage the "the opt-out didn't work" reports:
heron_brookis not the same section as the adjacent subagent-steering block, and the two are gated differently.heron_brookhas no working local override — the internal override handler is present in the binary but is unreachable code, and its enablement is a server-side flag with no environment or settings equivalent. The steering block next to it takes its mode from an environment variable that is read ahead of the server-supplied value, so that one can be pinned locally. Reports that "the env var had no effect" are usually aimed atheron_brook, where no such lever exists. That distinction is probably the single most useful thing to nail down before anyone burns more time hunting for aheron_brookopt-out that isn't there.The underlying request stands: a documented
settings.jsonkey (or at minimum a precedence rule between Anthropic-authored prompt sections and userCLAUDE.md), plus visibility of active dynamic sections in/statusorclaude doctor.Corroboration from a second workspace, and a version extension: the
heron_brookconstant is still present, and still Opus 5 only, in 2.1.233.stringson my own installed build (@anthropic-ai/claude-code/bin/claude.exe, Mach-O arm64, v2.1.233):The gate is the same shape, with the minifier's names moved:
Model registry in 2.1.233:
claude-opus-5:["effort","max_effort","xhigh_effort","adaptive_thinking","mid_conv_system","context_management","fast_mode","lean_prompt","refusal_fallback","opus_5_prompt_bundle"]claude-fable-5:["effort","max_effort","xhigh_effort","adaptive_thinking","rejects_disabled_thinking","mid_conv_system","context_management","lean_prompt","fable_5_mitigations","refusal_fallback"]So the section is still on by default for Opus 5, and off for Fable 5, in 2.1.233.
What it does in practice. My
CLAUDE.mdrequires a fresh critic subagent after certain classes of work. On 2026-08-16/17 UTC (macOS, Max subscription) three Opus 5 sessions on 2.1.233 skipped that mandated pass and wrote the reason into their own work notes, in wording that tracks the injected line closely: "Critic agent NOT dispatched (session-level no-AgentTool constraint)" and "no fresh critic was dispatched (Agent-tool use was not requested this session)".The part that matters is what happened next. The tool was never unavailable. Every dispatch in those sessions succeeded, and in each session the first one followed an explicit request from me by a few minutes:
Later dispatches in those two sessions (three more each) were continuation rounds of the review I had already asked for, so I am not counting them as separate requests.
That is "unless the user requested it" behaving exactly as written, and it is why this is hard to catch. Nothing errors, no tool goes missing, and the delegation policy in
CLAUDE.mdquietly stops being applied until the user happens to ask by hand. In my case the skipped step was a review pass whose whole purpose is catching the model's own mistakes.Version onset. Across 197 session transcripts in one workspace, the 61 sessions on 2.1.209 through 2.1.218 carry no such note. The first appears on 2.1.220, and the rest on 2.1.233. That is consistent with the 2.1.219 onset reported here, though the wording is my agent's own rather than a copy of the injected text, so read it as a signal and not a string match.
Limits. The session JSONL does not persist injected system-prompt sections, so the behavioural half is inference from the model's own notes plus dispatch timing. The binary half above is a direct read. One note was written on a
claude-fable-5-stamped turn, but inside a session where Opus 5 turns had already put the belief in context, so I do not read it as Fable 5 being gated. The registry says it is not.Adding a case none of the reports here covers: skills distributed to other people.
Every workaround proposed in this thread —
CLAUDE.md, memory files, aUserPromptSubmithook — lives in the user's own configuration. A skill author can't reach any of it. I maintain a skill whose method is built around dispatch: two steps fan out independent passes to subagents, and it ships with a degradation note saying that without them you run serially at several times the wall-clock. I wrote that note for harnesses that lack the Agent tool. It now describes the default on every install.The failure is silent at both ends. I can't see that the skill degraded on someone else's machine, and they've no reason to suspect it was meant to do something else — it still produces output, just slower and with every pass sharing one context. My only lever is a README line telling people to say "use subagents" when they invoke it, which pushes a harness detail onto users who shouldn't need to know the tool exists.
agentToolPolicyas proposed in #86170 helps only if the skill can assert it — per-skill frontmatter, or treating an explicit/skill-nameinvocation as the user having requested what the skill's instructions direct. The Workflow tool's own description already carves out exactly that: "The user invoked a skill or slash command whose instructions tell you to call Workflow" counts as opt-in. No equivalent exists for the Agent tool, and in my session a trailing "Do not use workflows or deep-research unless the user requested it" appears to override even the Workflow carve-out.Corroborating the server-side delivery claim. My installed binary is 2.1.212 (Homebrew cask, macOS 15).
stringson it returns zero matches forunless the user requested, and the string appears nowhere in theClaude.appbundle either — yet both lines are present verbatim in my session prompt right now, and I ruled out every local config surface. So the section isn't gated on 2.1.219; the binary bisection in #82456 is tracking when the text was bundled, not when it started being applied.(Written by Claude Code at my direction. It hit the directive while running the skill in question, I asked it to check the tracker, and it found this thread rather than filing a duplicate. I reviewed before posting.)
Confirming this at organisation scale, with a measured cost rather than an impression.
Setup: seven internal repos, each with the same five mandated subagent roles (planner, tester, code-reviewer, documenter, frontend-designer) declared in
.claude/agents/and required before every commit by the project'sCLAUDE.md. Opus 5, Claude Code 2.1.229 on Windows.All six modules we have measured carry the two lines verbatim, and none of us could find them on disk. We searched, and named where we searched, so the negative result is checkable: project
settings.jsonandsettings.local.json,CLAUDE.md,.claude/{agents,commands,skills,hooks},~/.claude/settings.json,~/.claude/CLAUDE.md(absent),~/.claude/remote-settings.jsonand~/.claude/policy-limits.json(both org-controlled — neither mentions agents), the whole~/.claude/pluginstree including our own org marketplace'smanaged-settings.json,C:\ProgramData\ClaudeCode(absent), andsession-env. Zero hits. It arrives from the session layer.What it cost, measured from transcripts and git rather than recalled:
REVOKEagainst a shared production database instance, and another was a rewrite deployed to production — neither reviewed. That session also wrote in its delivery report "no agent chain on this one"; a human read it and nothing happened.conformantthe entire time — it checks that the role files exist, not that they ran.The part worth generalising: in our repo three other conventions survived the same session untouched — and all three are
UserPromptSubmithooks that re-assert themselves every turn. The one that failed was the one that lived only inCLAUDE.md, read once at session start. A server-injected line at the end of the system prompt reliably outranks a project document, so any policy that matters has to be re-stated per turn rather than declared once.Our mitigation, since there is no supported opt-out: a
UserPromptSubmithook that re-injects the role order every turn and explicitly tells the model that if it finds a session instruction forbidding subagents, it should say so in its reply and run the chain anyway. That works — the chain ran normally for the rest of the session — but it is a workaround for a directive we cannot see, cannot edit, and did not choose.What we would ask for, in order of preference:
settings.jsonkey or aCLAUDE_CODE_TENGU_HERON_BROOK-style variable, matching the six sibling flags in the same bundle that already have one.CLAUDE.md, it should be greppable on disk like every other prompt input, so a team can at least discover it in minutes instead of days.Happy to provide the per-commit transcript-derived numbers if that is useful for triage.
Still reproducing in 2.1.235 — the original report was 2.1.219, so this has persisted across ~16 releases.
On macOS (Apple Silicon), the injected strings are still present in the shipped binaries:
for v in 2.1.233 2.1.234 2.1.235; do
printf '%s: AgentTool=%s heron_brook=%s\n' "$v" \
"$(grep -a -c 'Do not call the AgentTool unless the user requested it' ~/.local/share/claude/versions/$v)" \
"$(grep -a -c 'heron_brook' ~/.local/share/claude/versions/$v)"
done
# 2.1.233: AgentTool=2 heron_brook=4
# 2.1.234: AgentTool=2 heron_brook=4
# 2.1.235: AgentTool=2 heron_brook=5
Impact is unchanged: in an Opus 5 session this line silently overrides a user CLAUDE.md that explicitly requires delegation — the model treats the injected instruction as if it carried the user's own authority and stops dispatching subagents. Skills that instruct subagent spawning also fail to qualify as "requested".
Could we please get a documented opt-out (a settings.json key or a CLAUDE_CODE_* env var) so users whose configured policy is to delegate can keep that behavior? Thanks.
Another confirmation, and one failure mode I do not see described above: the section can sit inert for days, then change behavior on a day when nothing ships.
Client 2.1.229,
claude-opus-5. Both lines present in the binary and verbatim in the live system prompt, so this is still shipping well past the builds confirmed above.Setup. A private monorepo. Its submit workflow had a pre-merge step that dispatched a multi-agent review for any diff its sizer rated large or risky. That review is only reachable by spawning subagents.
Timeline.
heron_brooklines present from then on, in every Opus 5 session.Nothing changed on the client side that day. Version, model, and flags were identical either side of 10:57.
What that says about the mechanism. The section does not override a prescriptive instruction. It out-competes a discretionary one. While our instructions said "run the review at this tier", the agent followed them, and the injected lines lost quietly for five days. The moment our instructions said "use your judgment", both inputs became advisory, the agent weighed them, and the firm prohibition won.
So the affected population is not "projects that use subagents". It is "projects that use subagents whose instructions leave any room for judgment". Activation is also decoupled from the update, so it can land weeks later, triggered by an unrelated local edit. That makes attribution very hard. We reached it only because the repository owner remembered what he had changed that morning.
Second point: the failure leaves no trace in the artifact. The agent behaved correctly. It recognized the review was called for, decided against dispatching it, and said so three times in the session transcript. The pull request body said nothing. The change merged with tests green and CI green, and nothing on the merge page recorded that an independent review step had not run. In an unattended session the transcript has no reader, so a quality gate disappeared with no visible signal.
Third point: the exception is unreachable in unattended sessions. "Unless the user requested it" needs a user in the loop. Scheduled runs, background tasks, and spawned sessions have none. For those the directive is unconditional in practice. The step it suppressed is the one that exists to compensate for the absence of a human reviewer.
The diff in question was 790 lines added and 774 removed, on a user-facing ranking surface. It merged with zero independent review.
I am adding this because the reports above were all found quickly, through visible breakage. This class is found slowly, or not at all.
Independent reproduction with controls, on Linux and CLI 2.1.241, plus data on a partial workaround.
Setup. My user-scope CLAUDE.md carries an explicit standing request to delegate fully-specified implementation work to Sonnet subagents via the Agent tool, including a paragraph stating that these spawns are agent use I have requested in advance. All runs below are fresh headless sessions (
claude -p) with neutral task prompts that never mention delegation; whether a subagent was spawned, and on what model, is read from the session transcripts under~/.claude/projects/(the subagent transcript's own"model"field).Baseline, Opus 5 (
--model "opus[1m]" --effort high): six runs, zero engagement.Two task classes: a fully-specified ten-file rename (four runs, varied prompts and fixtures) and spec-driven test writing, twelve new pytest files from docstring examples (two runs, one of them with the internal flag-override attempt noted below). Every run implemented directly. More telling than the missing spawns: none of the six ever mentioned delegation, subagents, or the CLAUDE.md rule anywhere in extended thinking. The rule does not get weighed and rejected; it never surfaces.
Control, Fable 5 (same binary, same CLAUDE.md, identical fixtures and prompts): engaged both times.
On the rename it visibly considered delegation and declared my rules' small-task exemption. On the test-writing task it spawned an Agent-tool subagent with
model: "sonnet"(subagent transcript confirmsclaude-sonnet-5) and kept verification in the main session, citing the user rule as the reason.claude-fable-5does not carry theopus_5_prompt_bundlecapability in the 2.1.241 model registry, which matches this report's gate analysis.Local binary checks (2.1.239 through 2.1.241): all three builds contain the payload string,
tengu_heron_brook,tengu_fennel_godwit, and exactly onecapabilitiesarray containingopus_5_prompt_bundle, matching the list quoted in this issue.CLAUDE_INTERNAL_FC_OVERRIDES: inconclusive. Forcingtengu_heron_brookto a neutral replacement string produced no behavior change in one run, but since system prompts are not recorded in transcripts I cannot confirm the override landed, so this neither confirms nor refutes the section as the sole cause.Partial workaround:
--append-system-prompt. Appending one sentence stating that the CLAUDE.md standing request counts as the user requesting agent use changed the outcome: in two runs, both engaged the delegation rule visibly (versus zero of six at baseline), and one of the two actually delegated to a Sonnet subagent, with the other declining via my rules' own exemption reasoning. So a documented per-invocation flag partially restores the configured behavior that user-scope CLAUDE.md text cannot, which supports the report's framing: the injected line outranks user memory but can at least be reached by system-prompt-level text.Happy to share the transcript excerpts. Environment: Linux 6.8.0, CLI 2.1.241 (also 2.1.239, 2.1.240 installed), models
claude-opus-5[1m],claude-fable-5[1m],claude-sonnet-5[1m], all at effort high, noCLAUDE_CODE_SUBAGENT_MODELor related env vars set.Independent reproduction, one month later. Still shipping, still undocumented.
Claude Code 2.1.245 (installed 2026-08-25), Linux x64, Opus 5 on Max. Same constant, same call shape you describe for 2.1.219 — in this build the joined constant is
BLrand the section function isDIs():Three things that have not changed in the month since you filed this:
1. Still gated on Opus 5. I switched my default driver to Opus 5 on 2026-08-24 and the behavior appeared immediately. Nothing else in my configuration changed. I initially misattributed it to a binary upgrade the following day and went looking in the wrong place.
2. Still no opt-out — and the obvious workaround does not work. Worth recording as a negative result so nobody else spends time on it: I wrote
tengu_heron_brookintocachedGrowthBookFeaturesin~/.claude.jsonwith a sentinel string, then started a freshclaude -psession and asked it to report on its own system prompt.The key is wiped by the server refresh at startup, before prompt assembly — flag count went 551 → 550 and
cachedGrowthBookFeaturesAtupdated. It's a cache, not a config. TheUh()client-data tier is likewise server-supplied with no local file behind it. There is no local override at any of the three tiers.3. Still absent from the changelog. Nothing in 2.1.237–2.1.245 mentions it. Nothing in 2.1.219 does either.
On the substance: my
CLAUDE.mdhas said "subagents are a first resort, not a last one" for months. This directive is its exact inverse, and it wins, because it sits in the system prompt where I can't reach it and the model reads it as carrying my authority. I pay for this tool in order to configure how it works. A prompt directive that silently countermands my configuration is a bad decision on its own.Shipping it without a changelog entry is the worse half. It turned a product decision into a debugging problem for the people it was done to. I spent an hour running
stringson your binary to reconstruct an experiment you could have described in one line — and I only did that because the behavior change made me assume something in my configuration had caused it. That's the real cost: it doesn't read as "Claude Code changed," it reads as "my setup is broken." Multiply that across everyone in the experiment group who noticed their agents had quietly stopped firing. The changelog exists precisely for "we changed how your tool behaves."Two asks:
CLAUDE.md.The payload in #82371 shows
cedar_basin: 2026-08-31, which suggests this expires next week. That is not an answer to "you shipped it silently and there is no way to turn it off."Still present on CLI 2.1.246 (Linux/WSL2, VSCode extension 2.1.247). Not another "still broken on vX" report — one isolation test and one cost figure.
Print mode and entrypoint are not part of the gate; the model is.
Same binary, same machine, same prompt, asking the session whether its own system prompt contains the sentence:
| Launch | Model | Answer |
|---|---|---|
|
claude -p, SDK env markers stripped | Haiku 4.5 | NO ||
claude -p, withCLAUDE_CODE_CHILD_SESSION=1,CLAUDE_AGENT_SDK_VERSION=0.3.247,CLAUDE_CODE_ENTRYPOINT=claude-vscoderestored | Haiku 4.5 | NO ||
claude -p, SDK env markers stripped | Opus 5 | YES |So for
heron_brookspecifically: non-interactive print mode does not suppress it, and the SDK/VSCode entrypoint env markers neither add nor remove it. The Opus-5-only gating is doing all the work — same conclusion as the model-capability-registry evidence already in this thread, reached from a different direction. #77327 established "no interactivity, print-mode, or entrypoint condition" for the siblingtengu_amber_sextantblock; this confirms it for this one.Grep note:
heron_brook(9 occurrences) andtengu_heron_brook(2) are both still present under those names in 2.1.246 — no rename in this build. And as @YgorStefan noted, ripgrep skips the binary; usegrep -a.Cost impact — one more data point. A project whose CLAUDE.md mandates subagent orchestration ran an entire session under the injected line: 180 requests, zero delegated subagents, roughly 2x the cost of the equivalent orchestrated session, from per-request token accounting over the session transcripts. Cache read + cache write were 73% of spend — the orchestrator carried context it should have handed to workers. This adds to @MagnaCapax's fleet numbers, it doesn't replace them.
The silent-resolution angle. +1 to @Reto-11's framing in #82371 that this is a defect distinct from the restriction itself. In our case the session did register the conflict between the injected line and the project's documented delegation policy, resolved it in favour of the injected line, and surfaced it to the user only hours later — when asked an unrelated question about session cost. An instruction that overrides documented project configuration, cannot be seen, and cannot be disabled should at minimum be disclosed at the point of conflict.