Subagent tool_use_result.usage is empty for custom-provider models while Claude-native agents report usage correctly
Summary
When a subagent (Task/Agent tool) is invoked with a custom-provider model (routed via LiteLLM / ANTHROPIC_BASE_URL), the tool_use_result event returned to the parent session completely omits per-call usage:
tool_use_result.usage = {}(empty object)tool_use_result.totalTokens = nulltool_use_result.resolvedModelis correct
Meanwhile, Claude-native subagents (e.g., general-purpose → claude-sonnet-5) correctly return a full usage breakdown in tool_use_result.usage including input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, and iterations[].
The task_notification usage block for custom-provider agents also only contains a combined total_tokens (often zero or inconsistent) without input_tokens/output_tokens split — but the primary gap is the empty tool_use_result.usage, which is the structured completion payload the parent session receives.
Reproduced on Claude Code 2.1.235 and 2.1.236. No fix between versions.
---
Minimal Reproduction
Prerequisites
- A custom provider model alias configured (e.g., via LiteLLM) that resolves to a non-Anthropic endpoint
- An agent template referencing that model alias (e.g.,
model: work-nemotron-1)
Control — Claude-native subagent (WORKS)
claude -p "Run a subagent (Task tool) that reads a test file. Use the 'general-purpose' agent type." \
--output-format stream-json --verbose
tool_use_result (sanitized):
{
"type": "user",
"tool_use_result": {
"status": "completed",
"agentId": "agent-...",
"agentType": "general-purpose",
"resolvedModel": "claude-sonnet-5",
"totalDurationMs": 5035,
"totalTokens": 44927,
"totalToolUseCount": 1,
"usage": {
"input_tokens": 2,
"cache_creation_input_tokens": 1766,
"cache_read_input_tokens": 43110,
"output_tokens": 62,
"output_tokens_details": { "thinking_tokens": 0 },
"server_tool_use": { "web_search_requests": 0, "web_fetch_requests": 0 },
"service_tier": "standard",
"cache_creation": { "ephemeral_1h_input_tokens": 0, "ephemeral_5m_input_tokens": 1766 },
"inference_geo": "not_available",
"iterations": [
{ "input_tokens": 2, "output_tokens": 62, "cache_read_input_tokens": 43110, "cache_creation_input_tokens": 1766, "cache_creation": { "ephemeral_5m_input_tokens": 1766, "ephemeral_1h_input_tokens": 0 }, "type": "message" }
],
"speed": "standard"
}
}
}
Repro — Custom-provider subagent (BROKEN)
claude -p "Run a subagent (Task tool) that reads a test file. Use the 'work-a' agent type." \
--output-format stream-json --verbose
(where work-a has model: work-nemotron-1 routing via LiteLLM)
tool_use_result (sanitized):
{
"type": "user",
"tool_use_result": {
"status": "completed",
"agentId": "agent-...",
"agentType": "work-a",
"resolvedModel": "work-nemotron-1",
"totalDurationMs": 8424,
"totalTokens": null,
"totalToolUseCount": 1,
"usage": {}
}
}
task_notification (sanitized):
{
"type": "system",
"subtype": "task_notification",
"task_id": "agent-...",
"status": "completed",
"usage": { "total_tokens": 0, "tool_uses": 1, "duration_ms": 8424 }
}
---
Expected vs Actual
| Field | Control (Claude-native) | Repro (Custom-provider) | Expected Parity |
|-------|-------------------------|-------------------------|-----------------|
| tool_use_result.usage | Full breakdown object | {} (empty) | Must match |
| tool_use_result.usage.input_tokens | 2 | absent | Must be present |
| tool_use_result.usage.output_tokens | 62 | absent | Must be present |
| tool_use_result.usage.cache_creation_input_tokens | 1766 | absent | Must be present |
| tool_use_result.usage.cache_read_input_tokens | 43110 | absent | Must be present |
| tool_use_result.usage.iterations[] | populated | absent | Must be present |
| tool_use_result.totalTokens | 44927 | null | Must be populated |
| tool_use_result.resolvedModel | claude-sonnet-5 | work-nemotron-1 | ✅ Works |
| task_notification.usage.total_tokens | (not emitted for control) | 0 | Secondary; see #85890 |
---
Version Comparison (2.1.235 → 2.1.236)
| Agent / Model | 2.1.235 task_notification.total_tokens | 2.1.236 task_notification.total_tokens | tool_use_result.usage |
|---------------|------------------------------------------|------------------------------------------|-------------------------|
| general-purpose / claude-sonnet-5 | 44,488 | 44,883 | ✅ Full in both |
| work-a / work-nemotron-1 | 9,664 | 0 | ❌ Empty in both |
| work-b / work-nemotron-2 | 38 | 0 | ❌ Empty in both |
| work-c / work-nemotron-3 | 0 | 38 | ❌ Empty in both |
| auditor / gpt-5.6-terra | 0 | 0 | ❌ Empty in both |
No structural fix between versions. The tool_use_result.usage remains empty for all custom-provider models.
---
Impact
- Cost accounting impossible for custom-provider subagents — no input/output/cache split available in the parent session's completion payload.
- Observability tools relying on
tool_use_result.usage(the documented structured completion record) receive empty data for non-Claude models. - No local workaround — the backend (LiteLLM/provider) returns real usage per call, but the SDK does not surface it in the subagent completion event.
---
Related Issues
- #85890: Requests input/output split in
task_notification(enhancement) - #84223: Subagent transcripts missing final usage (bug)
- #84705: Requests richer
task_notificationwith resolved model (feature)
This issue is distinct: it is about the tool_use_result event (the synchronous completion payload returned to the parent tool call) being empty for custom-provider models, while it is fully populated for Claude-native models. The task_notification is an async notification; tool_use_result is the primary structured result of the Agent tool call.