Repository navigation
Description
Summary
When the session model and advisorModel are both opus, the advisor-injection gate accepts the pairing because its two whitelists are byte-identical and there is no tier comparison. The advisor is also injected into every subagent (Task / Agent) context — so a 3-subagent opus team silently spawns ~4 extra same-tier opus calls that never appear in /context, never roll up to the orchestrator's token accounting, and emit no log line. The behavior directly contradicts the advisor docstring, which describes a "stronger reviewer model."
Expected behavior
The bundled advisor docstring (Vt9 in 2.1.138) states:
You have access to an
advisortool backed by a stronger reviewer model. It takes NO parameters -- when you call advisor(), your entire conversation history is automatically forwarded.
The implied contract:
- Advisor calls go to a strictly stronger tier than the consulting context.
- Same-tier (opus → opus) or weaker-tier advisor pairings should be rejected.
- Costs incurred by
advisor()should be observable to the user, either in/contextor in the subagent summary returned to the orchestrator.
Actual behavior
baseModel = claude-opus-4-7 paired with advisorModel = claude-opus-4-7 is accepted, and the same gate runs in every subagent context. With opus subagents the cost compounds:
Orchestrator (opus)
├── advisor() → opus ← 1 invisible call
├── Subagent: planner (opus)
│ └── advisor() → opus ← 1 more invisible call
├── Subagent: architect (opus)
│ └── advisor() → opus ← 1 more invisible call
└── Subagent: executor (opus)
└── advisor() → opus ← 1 more invisible call
A 3-subagent opus team produces ~8 opus turns (4 worker turns + 4 advisor turns) where the user expects 4. Each advisor call duplicates the consulting agent's full transcript into a same-tier model, so token spend roughly doubles for no marginal capability gain.
Why it's hard to detect
- Advisor calls inside subagents run in nested contexts. Their tokens do not appear in the main session's
/contextview. - The orchestrator sees
subagent: <name> (opus) — completedwith no breakdown of advisor calls made during the run. - No log line, no warning, no per-tool budget attribution. The first signal is the billing statement.
- Subagent advisor calls also don't roll up into the orchestrator's context-window accounting, so users actively watching token usage still see nothing.
- The advisor docstring suggests "stronger model," so users reasonably assume same-tier pairings are blocked by the gate.
Reproduction steps
- Run Claude Code with
model = claude-opus-4-7andadvisorModeleither unset (defaults to the session model when opus) or explicitly set to an opus tier insettings.json. - Start any task that spawns an opus subagent via
Task/Agent. - Inside the subagent, take an action that triggers an
advisor()call (any substantive decision point in a multi-step task — the advisor tool is exposed in subagent contexts). - Observe: the advisor call succeeds and runs on opus. The call does not appear in the orchestrator's
/context, and the subagent's return summary contains no advisor accounting.
Observed on Claude Code 2.1.138 (macOS arm64).
Root cause (code level)
In the 2.1.138 bundled JS, Zt9 is the advisor-injection gate. It contains no baseModel vs advisorModel tier comparison:
function Zt9(advisorModel, baseModel) {
if (!lC() || !advisorModel) return;
let q = normalize(advisorModel);
if (!D$H(baseModel)) return; // baseModel whitelist
if (!iA_(q)) return; // advisorModel whitelist
return q;
}
function D$H(H) {
return H.toLowerCase().includes("opus-4-7")
|| H.toLowerCase().includes("opus-4-6")
|| H.toLowerCase().includes("sonnet-4-6");
}
function iA_(H) {
return H.toLowerCase().includes("opus-4-7")
|| H.toLowerCase().includes("opus-4-6")
|| H.toLowerCase().includes("sonnet-4-6");
}The two whitelists are byte-identical. There is no ordered tier mapping (e.g. sonnet-4-6 < opus-4-6 < opus-4-7), so the gate cannot reject same-tier pairings. The same Zt9 codepath is reused when the advisor tool is injected into subagent contexts; there is no subagent-specific guard.
Related symbols in the same binary:
Vt9— advisor tool docstring ("stronger reviewer model")lC()— env-toggle gate (CLAUDE_CODE_DISABLE_ADVISOR_TOOL,CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL)- Server-side tool name:
advisor_20260301
Source: ~/.local/share/claude/versions/2.1.138 (Mach-O bundled JS, decomp via strings).
Proposed fixes
1. Minimum fix — tier guard in Zt9. Add an ordered tier mapping and require the advisor to be strictly stronger than the base model. modelTier() below does not exist in 2.1.138; it needs a small new helper that maps the existing whitelist entries to ordered tiers (e.g. sonnet-4-6 < opus-4-6 < opus-4-7):
// Proposed — modelTier() is new; maps whitelist entries to ordered tiers.
function Zt9(advisorModel, baseModel) {
if (!lC() || !advisorModel) return;
let q = normalize(advisorModel);
if (!D$H(baseModel)) return;
if (!iA_(q)) return;
if (modelTier(q) <= modelTier(baseModel)) return; // require strictly stronger advisor
return q;
}2. Stronger fix — don't inject advisor into subagent contexts by default. Subagents are already purpose-scoped by the orchestrator, so the advisor relationship doesn't need to propagate transitively. Require explicit per-subagent opt-in (a flag on the Task call) if subagent-level advisor remains a supported workflow.
3. Observability fallback (independent of either fix above). Surface advisor invocations (count + tokens) in the subagent summary returned to the orchestrator, and include nested advisor token counts in /context. Even before the tier or propagation behavior changes, this makes the spend observable rather than invisible. This is the cheapest change of the three and useful regardless of which structural fix is taken.
The three are not mutually exclusive: (1) closes the same-tier hole, (2) prevents transitive amplification, (3) prevents future regressions of the same shape from being undetectable.
Environment
- Claude Code: 2.1.138
- Platform: macOS arm64 (also reportedly affects Linux on earlier 2.1.x)
- Repro independent of any third-party tooling; multi-agent sessions amplify the signal
Activity
fitz-s commentedon May 12, 2026
Tagging for triage: this is a confirmed-reproducible bug affecting cost visibility in multi-agent sessions.
Suggested labels if a maintainer can apply:
bug,has repro,area:cost,area:agents,area:model,high-priorityThe subagent multiplication angle (§2) is the novel part not covered in prior reports — same-tier advisor calls inside subagents are completely invisible to users and compound with every parallel worker spawned.
[-]Advisor tool injects same-tier opus calls into every opus subagent, doubling spend invisibly[/-][+][BUG]Advisor tool injects same-tier opus calls into every opus subagent, doubling spend invisibly[/+]yurukusa commentedon May 12, 2026
The same invisible-cost pattern is the design at the subagent boundary more broadly. When subagents run, their tool calls don't roll up to the orchestrator's
/contextview; each subagent has isolated context accounting. Advisor inside a subagent is the case where this hits hardest because the call is implicit (no explicit tool invocation in user-visible trace), so the cost has no signal at all.Pragmatic mitigations while waiting for a tier-comparison fix in
Zt9:advisorModelexplicitly to a weaker tier in settings.json (sonnet or haiku). The gate accepts the pairing because the two whitelists are byte-identical, as your analysis notes, so unilaterally downgrading doesn't break it.tools:allowlist to exclude it. Subagent tool restrictions in frontmatter are honored (the inheritance-tester [BUG]memory:field in subagent frontmatter not functional — reproduces on v2.1.137; tools allowlist appears to override auto-enable #57507 pattern, but in this case used in your favor).Your "Why it's hard to detect" section maps to a broader shape that's surfaced in the recent changelog activity: subagent-scope events that don't surface in the orchestrator's view. The model behavior is consistent with
Zt9's docstring wording; the visibility layer is the structural gap.ThatDragonOverThere commentedon May 12, 2026
After swearing I was done paying Anthropic to be their lead QA tester, done with daily CLI upgrades until they proved stable (fucks sake, you have my contact information, just put me on the payroll or at least start comping my accounts), I saw the PM upgrade yesterday that seemed like it might be a response to my feature request for overnight work tools that might actually get something done. So I upgraded.
The same thing happened to me, overnight on v2.1.139.
I had 10 agent-mode windows running — one Opus PM orchestrating workers, the rest configured as Sonnet. By morning, 80% of my weekly usage was gone. I filed the full incident as #58450.
Your issue explains part of what I could not account for. If advisor is injecting same-tier Opus calls into every subagent turn invisibly, and my PM was running Opus, the compounding works out exactly as you describe — except I had no idea it was happening because nothing in the session UI surfaced it. Not in /context, not in the token count, not anywhere.
What made it worse: the model picker showed Sonnet on the first view for every window. It is only when you continue the model command to the second step that it reveals Opus as the active tier. So I went to sleep believing I had Sonnet workers. The billing ran Opus. The advisor compounding ran on top of that. The first signal was the usage wall at 12:20 AM.
The advisor docstring says 'stronger reviewer model.' If same-tier is allowed and compounds across every subagent with no visibility, that docstring is misleading and the billing impact is catastrophic for overnight multi-agent runs. This needs to be fixed alongside the model display mismatch — they are hitting the same overnight use case and multiplying the damage together.
yurukusa commentedon May 17, 2026
@ThatDragonOverThere — the overnight 80% wipeout you described is the worst-case shape of this cluster, and the three concurrent mismatches you named are the right diagnosis. Following up with a concrete operator-side mitigation since the underlying surfaces (model picker UI, advisor docstring,
/contextvisibility) are still vendor-side.The single configuration change that closes the largest portion of the exposure is setting
advisorModelexplicitly to a weaker tier in~/.claude/settings.json:{ "advisorModel": "sonnet" }or
"haiku"for the lowest tier. The advisor gate accepts whatever pairing is configured, including a same-tier pairing as you found out the hard way, but it also accepts unilateral downgrades because the two whitelists are byte-identical at the gate level. Setting it explicitly to sonnet (or haiku) means the advisor invocations stop running Opus regardless of what the parent model is, which removes the compounding multiplier from the equation. It does not fix the model picker UI mismatch (that's display-layer) or the/contextvisibility gap (that's instrumentation-layer), but it does prevent the billing event itself.The harder part is catching the same configuration drift in subagents. Per-subagent frontmatter tools allowlists are honored at spawn time, so for any subagent that does not need advisor at all, you can exclude it explicitly in the frontmatter. The pattern that breaks this is when the subagent's frontmatter is silent on tool allowlisting and inherits whatever the parent had configured, which is the same root cause as the cross-product surface (
enabledPlugins: falsesilently no-opping in the v2.1.139 cluster).On the prevention-hook side, I shipped a SessionStart advisory today that reads your effective model + advisor configuration from
~/.claude/settings.json(plus project-level overrides) and warns when the parent is Opus-tier and the advisor is either unset or same-tier-or-higher. The hook is read-only and exits 0 — it does not block, it just makes the configuration drift visible to the agent at session start so it surfaces before a 10-window overnight run starts compounding. Reference implementation incc-safe-setupasadvisor-billing-safety.sh; the test suite covers 20 cases including the alternate JSON paths (permissions.advisorModel,env.ANTHROPIC_MODEL) and the environment-variable overrides (CLAUDE_MODEL,ANTHROPIC_MODEL).The piece that the hook does not catch is the one you flagged most precisely: the model picker UI showing Sonnet on the first view while Opus was actually active. That is a display-layer mismatch and no operator-side hook can fix it. The workaround until vendor-side fixes ship is to verify the active model from a second source before starting long-running multi-window sessions.
claude --versionfollowed by checking the session's first response for the model identifier in the assistant turn metadata is one path; checking/modelin an interactive session before starting the agent-mode windows is another. Both add friction but both are reliable.Your point about advisor invocations being invisible in
/contextand the token count is the highest-priority vendor-side fix. The Claim-Verify Handbook documents this cluster in Appendix D and the case structure is exactly what you described: a billing event with no visible signal until the quota wall is reached. The Handbook ships 2026-05-22 if you want the full cluster context; the operator-side mitigations are the same as above, with the hook stack as the floor and explicit per-session configuration verification as the ceiling.For the overnight multi-window pattern specifically, the safest configuration profile based on the cluster evidence is: parent set to whichever tier your work actually requires (often sonnet is sufficient for orchestration), advisor set explicitly to haiku, and per-subagent frontmatter tool allowlists that exclude advisor for any subagent that does the bulk-execution work. That profile produces an advisor-invocation cost equivalent to haiku per call rather than opus per call, which is approximately a 12x reduction at the per-advisor-call level and corresponds to roughly the same multiplier on the overnight quota burn you experienced.
The 80% number is the right floor for this class of event. The cluster's worst-case envelope under no-mitigation conditions is consistent with what you observed; the operator-side hook stack reduces but does not eliminate it. Until vendor-side visibility lands, your habit of "verify the active model before starting" is the highest-leverage single operator practice for this surface.
kcarriedo commentedon May 17, 2026
The "doubling spend invisibly" framing is the right one, and worth highlighting that this is structurally identical to a class of bug that keeps surfacing in the multi-agent runtime: nested costs that don't roll up into the parent's observability surface.
A couple of adjacent data points from running a scheduler that orchestrates multiple Claude Code agent invocations per cycle:
The cost-undercount surface is broader than the advisor tool. The same omission pattern shows up at the sub-agent boundary generally (see #55121, "Token counter appears to omit sub-agent consumption, causing up to 10× undercount"). Both bugs share the same root: nested invocations consume tokens that aren't attributed to the parent session's accounting surface. The advisor case is sharper because it's same-tier and gated by a check that's structurally unable to detect the failure (byte-identical whitelists), but the architectural class is identical — parent-tier accounting is not tree-aware. Fixing either one in isolation leaves the other still broken because the underlying attribution problem isn't solved.
The reliable workaround on our side has been to parse
~/.claude/projects/{slug}/{session_uuid}.jsonldirectly and sumusage.input_tokens + usage.cache_creation_input_tokens + usage.output_tokensacross every session file spawned by a cycle. The advisor calls are recorded in their own session JSONL (they have to be — billing has to see them somehow), but those files are per-session, not per-orchestration-unit. So even with JSONL parsing, the operator has to externally track the parent→child session graph (cycle_id, in our case) to compute a real cost number. Every operator running multi-agent orchestration is reimplementing this aggregation independently, and the numbers don't reconcile against/contextor the Desktop counter, which is exactly what makes runaway-spend incidents hard to debug.On the tier-comparison gap specifically: the byte-identical-whitelist check is a textbook case of the runtime measuring shape rather than substance (the same anti-pattern called out in #54393's cross-cutting themes: "integrity surfaces measure shape, not substance"). A model identifier match is a string equality check; what the docstring contract actually requires is a tier ordering check. Those are not the same operation. The fix is mechanical (replace whitelist-equality with a tier-ordering predicate that knows opus > sonnet > haiku), and the same fix probably wants to extend to any other "stronger model" gate in the codebase — the bug pattern likely isn't unique to the advisor tool.
On the silent-injection-into-subagents shape: this is structurally the same as the auto-distributor injecting pseudo-teammate messages into specialists (#59907 from today) and the skill tool not inheriting parent's Agent grant (#59968 from today). Three different surfaces, same anti-pattern: a runtime feature that should fail loudly when its invariants aren't met instead silently does something subtly wrong, and the operator first notices via a downstream symptom (wrong verdict, wrong spend, wrong work performed). Closing that anti-pattern at the runtime layer — explicit errors / hardFailure semantics whenever a nominally-available capability can't honor its contract — would cover a meaningful fraction of recent multi-agent reliability reports in one fix.
One concrete observability ask worth attaching to this issue: even a
usage.advisor_token_totalfield on the parent session's final assistant turn would unblock external cost tooling. Same shape as theusage.sub_agent_token_totalask in #55121 — operators don't need full per-call attribution to debug runaway spend, they need a single rolled-up number per orchestration unit. Right now the only signal is the billing statement, and that's a 24+ hour feedback loop on a class of bug that produces incidents in minutes.Bookmarked. Cross-referencing this with #55121 in our internal runbook for "subagent / advisor cost undercount" issues.
yurukusa commentedon May 18, 2026
@kcarriedo — the JSONL-direct parsing workaround is the right answer for now, and the parent-tier-accounting-is-not-tree-aware framing is the one Anthropic needs to internalise for the structural fix. A few notes on connecting this to the broader cluster, particularly around what's been articulated in the last 36 hours.
On the architectural class. Your observation that #58217 and #55121 share the underlying attribution problem is the key one. They look like different bugs (advisor vs sub-agent) but are surface symptoms of the same parent-tier accounting failure. The fix needs to live at the runtime layer that records token usage — not at either of the surface sites. If
~/.claude/projects/.../{session}.jsonlalready records the advisor invocations correctly (and it has to, for billing), then the structural fix is to make the parent session's/statusand/contextoutputs aggregate across all sessions spawned in the same parent context, not just the parent's own JSONL. The data exists; the aggregation across the tree doesn't.On the cost-recognition gap as part of the broader cluster. The advisor failure is also an instance of a structural pattern that was articulated cleanly on 2026-05-18 in #60226 (filed by @suwayama). The general shape: the model or runtime has recognition-relevant information and the system articulates that recognition somewhere observable, but the recognition does not gate the action. In your case:
claude --statusshould be the recognition surface. The runtime has all the data needed to display the true cost (advisor consumption is tracked in JSONL)./statusdoes not aggregate across the advisor sub-sessions. The recognition that "the parent's accounting is a subset of actual spend" exists in the JSONL files, is reachable by a tree-walking aggregation, and is not gating against the displayed total.The same structure shows up in the silent-quota-overrun cluster ([BUG]Advisor tool injects same-tier opus calls into every opus subagent, doubling spend invisibly #58217's parent thread), in Memory directives are loaded but not consistently honoured #59529 (memory directives loaded but not weighted), in Agent output and permission-prompt rate increase as work becomes mechanical, inverse to cognitive load #60188 (in-loop block messages loaded but not gating the next tool selection). The signal varies; the gating failure has the same shape.
On the JSONL workaround. For anyone reading this thread who needs to deploy the workaround under pressure, the schema differs slightly between sessions invoked via
claudeCLI vs Cowork. The CLI sessions land in~/.claude/projects/{path-slug}/{uuid}.jsonlwhere each line carries anassistantortool_resultevent with ausageblock. For correct aggregation you need:The third term (cache_read) is the one most often omitted in informal aggregations and accounts for the largest share of token cost in long sessions. Worth flagging if anyone is rolling their own monitor — informal scripts that sum only input + output will report a 2-4× undercount on cache-heavy workflows.
For the structural fix. The right shape is a runtime-level aggregation API that takes a parent session UUID and walks the tree of sub-session JSONLs. Once that exists, both
/statusand the advisor case and the general sub-agent case (#55121) get fixed by the same change. Treating these as different bugs and fixing them piecemeal will leave attribution gaps that re-emerge with the next nesting depth.(Adding #60226 as a structural-frame reference: the parent-tier accounting failure is one instance of the larger gating-from-recognition failure family. The cost-attribution gap and the runtime block message non-gating and the memory directive non-weighting are sibling shapes.)
github-actions commentedon Jun 17, 2026
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
github-actions commentedon Aug 20, 2026
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.