Agent SDK query() reports costUSD 3x higher than visible tokens for Opus 4.7
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 comparescostUSDagainst token counts from the sameModelUsageresponse — 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:
thinking: { type: 'disabled' }is not propagated correctly to the underlying API call for Opus 4.7 — the SDK passes--thinking disabledto 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.
ModelUsageis missing a token field — Opus 4.7 uses adaptive thinking (the only thinking mode it supports). If the SDK enables adaptive thinking despitedisabledbeing requested, the thinking tokens would be billed at the output rate but have no dedicated field inModelUsageto report them.
costUSDincludes 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.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗