Agent SDK query() reports costUSD 3x higher than visible tokens for Opus 4.7

Resolved 💬 3 comments Opened Apr 17, 2026 by xsrik Closed Apr 19, 2026

Bug: Agent SDK query() reports costUSD 3x higher than visible tokens explain for Opus 4.7

Summary

When using @anthropic-ai/claude-agent-sdk query() with thinking: { type: 'disabled' } and claude-opus-4-7, ModelUsage.costUSD is ~3x higher than the cost computed from the token fields in the same ModelUsage object (inputTokens, outputTokens, cacheCreationInputTokens, cacheReadInputTokens). 66.7% of the billed cost is unexplained.

This does not happen with:

  • Direct CLI (claude --thinking disabled --model claude-opus-4-7 -p) — cost reconciles perfectly
  • Agent SDK with claude-opus-4-6 — cost reconciles perfectly
  • Direct CLI with claude-opus-4-6 — cost reconciles perfectly

The bug is specific to Agent SDK query() + Opus 4.7.

Reproduction

Prerequisites

node -v   # >= 20
claude --version  # Claude Code CLI installed
echo $ANTHROPIC_API_KEY  # set

Minimal reproduction (~2 minutes, warm cache OK)

// repro.ts
import { query } from '@anthropic-ai/claude-agent-sdk';

const PROMPT = 'What is 2+2? Answer in one word.';

function computeCost(u: any): number {
  return (
    ((u.inputTokens ?? 0) / 1e6) * 5 +
    ((u.outputTokens ?? 0) / 1e6) * 25 +
    ((u.cacheCreationInputTokens ?? 0) / 1e6) * 6.25 +
    ((u.cacheReadInputTokens ?? 0) / 1e6) * 0.5
  );
}

async function run(model: string): Promise<void> {
  const q = query({
    prompt: PROMPT,
    options: {
      model,
      thinking: { type: 'disabled' },
      effort: 'high',
      permissionMode: 'bypassPermissions',
      allowDangerouslySkipPermissions: true,
      persistSession: false,
      maxTurns: 2,
    },
  });

  for await (const msg of q) {
    if (msg.type === 'result') {
      const r = msg as any;
      for (const [m, usage] of Object.entries(r.modelUsage ?? {})) {
        const u = usage as any;
        const computed = computeCost(u);
        const gap = u.costUSD - computed;
        const pct = u.costUSD > 0 ? ((gap / u.costUSD) * 100).toFixed(1) : '0.0';
        console.log(`${m}:`);
        console.log(`  inputTokens:              ${u.inputTokens}`);
        console.log(`  outputTokens:             ${u.outputTokens}`);
        console.log(`  cacheCreationInputTokens: ${u.cacheCreationInputTokens}`);
        console.log(`  cacheReadInputTokens:     ${u.cacheReadInputTokens}`);
        console.log(`  costUSD:                  $${u.costUSD.toFixed(6)}`);
        console.log(`  computed from tokens:     $${computed.toFixed(6)}`);
        console.log(`  unexplained gap:          $${gap.toFixed(6)} (${pct}%)`);
      }
    }
  }
}

async function main() {
  console.log('--- Opus 4.6 (should show 0% gap) ---');
  await run('claude-opus-4-6');
  console.log('\n--- Opus 4.7 (shows ~67% gap) ---');
  await run('claude-opus-4-7');
}

main().catch(console.error);
npx tsx repro.ts
Note: Cache state affects raw cost values but NOT the gap %. The gap compares costUSD against token counts from the same ModelUsage response — both come from the same API call. The 66.7% gap reproduces regardless of cache warm/cold state. The cold-cache 4-way table below (each run waited 5m10s) confirms this.

Expected output

Both models should show 0% gap — costUSD should equal the cost computed from the reported token counts using published pricing ($5/$25/$6.25/$0.50 per MTok).

Actual output

--- Opus 4.6 (should show 0% gap) ---
claude-opus-4-6:
  inputTokens:              3
  outputTokens:             5
  cacheCreationInputTokens: 13752
  cacheReadInputTokens:     0
  costUSD:                  $0.086090
  computed from tokens:     $0.086090
  unexplained gap:          $0.000000 (0.0%)     <-- OK

--- Opus 4.7 (shows ~67% gap) ---
claude-opus-4-7:
  inputTokens:              6
  outputTokens:             7
  cacheCreationInputTokens: 19001
  cacheReadInputTokens:     0
  costUSD:                  $0.356884
  computed from tokens:     $0.118961
  unexplained gap:          $0.237922 (66.7%)    <-- BUG

Verification: Direct CLI does NOT have this bug

The same prompt via claude CLI directly produces matching numbers for both models:

# Opus 4.6 — matches
claude --thinking disabled --effort high --model claude-opus-4-6 \
  --no-session-persistence --output-format json \
  -p "What is 2+2? Answer in one word." \
  | jq '.modelUsage'

# Opus 4.7 — also matches (0% gap)
claude --thinking disabled --effort high --model claude-opus-4-7 \
  --no-session-persistence --output-format json \
  -p "What is 2+2? Answer in one word." \
  | jq '.modelUsage'

Direct CLI results for Opus 4.7 (cold cache):

claude-opus-4-7:
  inputTokens:              6
  outputTokens:             7
  cacheCreationInputTokens: 27396
  costUSD:                  $0.171430
  computed:                 $0.171430
  gap:                      $0.000000 (0.0%)     <-- No bug via CLI

Full cold-cache 4-way comparison

Each run waited 5m10s for cache expiry. All runs show cacheReadInputTokens: 0 confirming cold cache:

| Path | Model | SDK costUSD | Computed from tokens | Gap |
|------|-------|-------------|---------------------|-----|
| Direct CLI | opus-4-6 | $0.054787 | $0.054787 | 0.0% |
| Agent SDK | opus-4-6 | $0.086090 | $0.086090 | 0.0% |
| Direct CLI | opus-4-7 | $0.171430 | $0.171430 | 0.0% |
| Agent SDK | opus-4-7 | $0.356884 | $0.118961 | 66.7% |

Pricing used for computation

From https://docs.anthropic.com/en/docs/about-claude/pricing (both models same rate):

| Token type | Rate |
|---|---|
| Input | $5.00 / MTok |
| Output | $25.00 / MTok |
| Cache creation (5min) | $6.25 / MTok |
| Cache read | $0.50 / MTok |

Formula: cost = (input/1M)*5 + (output/1M)*25 + (cacheCreate/1M)*6.25 + (cacheRead/1M)*0.50

Analysis

The gap is exactly consistent with ~9,500 output-rate tokens being billed but not reported in any ModelUsage field. Possible causes:

  1. thinking: { type: 'disabled' } is not propagated correctly to the underlying API call for Opus 4.7 — the SDK passes --thinking disabled to the CLI, but the CLI correctly respects it (proven by direct CLI test). Something in the SDK's process spawning or message handling path may override this for Opus 4.7.
  1. ModelUsage is missing a token field — Opus 4.7 uses adaptive thinking (the only thinking mode it supports). If the SDK enables adaptive thinking despite disabled being requested, the thinking tokens would be billed at the output rate but have no dedicated field in ModelUsage to report them.
  1. costUSD includes charges not itemized in token fields — some internal processing cost specific to the SDK path for Opus 4.7.

Note: Opus 4.7 defaults to thinking.display: "omitted" (thinking blocks have empty text), so even if thinking IS running, no thinking content would be visible in the response stream.

Environment

@anthropic-ai/claude-agent-sdk: 0.2.110
Claude CLI: 2.1.112 (Claude Code)
Node: v24.13.1
Platform: linux x64

Impact

Users relying on ModelUsage to track costs or compare models will see misleading numbers. The costUSD field is correct (matches actual billing), but the token fields don't explain ~67% of the cost, making it impossible to understand or optimize spending.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗