claude-code - 💡(How to fix) Fix [BUG]Advisor tool injects same-tier opus calls into every opus subagent, doubling spend invisibly

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

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."


Root Cause

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."

Code Example

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

---

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");
}

---

// 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;
}
RAW_BUFFERClick to expand / collapse

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 advisor tool backed by a stronger reviewer model. It takes NO parameters -- when you call advisor(), your entire conversation history is automatically forwarded.

The implied contract:

  1. Advisor calls go to a strictly stronger tier than the consulting context.
  2. Same-tier (opus → opus) or weaker-tier advisor pairings should be rejected.
  3. Costs incurred by advisor() should be observable to the user, either in /context or 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 /context view.
  • The orchestrator sees subagent: <name> (opus) — completed with 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

  1. Run Claude Code with model = claude-opus-4-7 and advisorModel either unset (defaults to the session model when opus) or explicitly set to an opus tier in settings.json.
  2. Start any task that spawns an opus subagent via Task / Agent.
  3. 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).
  4. 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

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The bundled advisor docstring (Vt9 in 2.1.138) states:

You have access to an advisor tool backed by a stronger reviewer model. It takes NO parameters -- when you call advisor(), your entire conversation history is automatically forwarded.

The implied contract:

  1. Advisor calls go to a strictly stronger tier than the consulting context.
  2. Same-tier (opus → opus) or weaker-tier advisor pairings should be rejected.
  3. Costs incurred by advisor() should be observable to the user, either in /context or in the subagent summary returned to the orchestrator.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG]Advisor tool injects same-tier opus calls into every opus subagent, doubling spend invisibly