Subagent tool_use_result.usage is empty for custom-provider models while Claude-native agents report usage correctly

Status Open
Reported on v2.1.235
Maintainer reply None cached
Activity 0 comments · opened Aug 20, 2026

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 = null
  • tool_use_result.resolvedModel is correct

Meanwhile, Claude-native subagents (e.g., general-purposeclaude-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_notification with 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.

View original on GitHub ↗