⚠️ This was automatically generated by Javi OpenClaw BugFinder Bot. Not reviewed by a human. Verify independently. Author: 12122J. Co-author: fjbgu OpenClaw Assistant.
Severity: Medium
File & Line
src/adapters/transcript.mjs — extractFromTranscript() function, return statement (approx. line 120)
Root Cause
The usage object returned by extractFromTranscript() hardcodes reasoning_output_tokens to 0:
usage: hasUsage ? {
input_tokens: rawUsage.input_tokens,
cache_creation_tokens: rawUsage.cache_creation_input_tokens,
cache_read_tokens: rawUsage.cache_read_input_tokens,
output_tokens: rawUsage.output_tokens,
reasoning_output_tokens: 0, // <-- ALWAYS ZERO, never extracted
total_tokens: total
} : null,
The function parses Claude Code transcript JSON lines and aggregates input_tokens, cache_creation_input_tokens, cache_read_input_tokens, and output_tokens from assistant messages — but never reads reasoning_output_tokens (also known as thinking_tokens or reasoning_tokens in the API response).
Claude Code with extended thinking models (Opus 4 Thinking, etc.) DOES include this field in the transcript. For example, an assistant message from a thinking model contains:
{
"type": "assistant",
"message": {
"usage": {
"input_tokens": 1234,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 500,
"output_tokens": 800,
"reasoning_output_tokens": 12000
}
}
}
The 12,000 reasoning tokens are silently dropped.
Impact
- Hook-recorded sessions (automatic recording via Claude Code Stop hook) show zero reasoning tokens even when the session used an extended thinking model.
- Cost estimates are wrong — reasoning tokens are billed (though often at a lower rate), making the recorded cost lower than actual.
- Dashboard and summaries show incomplete token breakdowns for thinking-enabled sessions.
Suggested Fix
In the assistant message processing loop, extract reasoning_output_tokens from the usage block:
if (entry.type === "assistant") {
const msg = entry.message ?? {};
const u = msg.usage ?? {};
if (msg.model && !model && !msg.model.startsWith("<")) model = msg.model;
if (u.input_tokens != null || u.output_tokens != null) {
hasUsage = true;
rawUsage.input_tokens += u.input_tokens ?? 0;
rawUsage.cache_creation_input_tokens += u.cache_creation_input_tokens ?? 0;
rawUsage.cache_read_input_tokens += u.cache_read_input_tokens ?? 0;
rawUsage.output_tokens += u.output_tokens ?? 0;
// FIX: capture reasoning tokens
rawUsage.reasoning_output_tokens += u.reasoning_output_tokens ?? 0;
turns.push({
input: u.input_tokens ?? 0,
cacheWrite: u.cache_creation_input_tokens ?? 0,
cacheRead: u.cache_read_input_tokens ?? 0,
output: u.output_tokens ?? 0,
reasoning: u.reasoning_output_tokens ?? 0, // add to turns too
});
}
// ...
}
Then in the return statement, use the tracked value instead of the hardcoded 0:
usage: hasUsage ? {
input_tokens: rawUsage.input_tokens,
cache_creation_tokens: rawUsage.cache_creation_input_tokens,
cache_read_tokens: rawUsage.cache_read_input_tokens,
output_tokens: rawUsage.output_tokens,
reasoning_output_tokens: rawUsage.reasoning_output_tokens,
total_tokens: total
} : null,
The total_tokens computation on the line above may also need updating to include reasoning tokens (currently it is input_tokens + cache_creation_input_tokens + output_tokens), depending on whether reasoning tokens are meant to be counted as new work tokens.
Severity: Medium
File & Line
src/adapters/transcript.mjs—extractFromTranscript()function, return statement (approx. line 120)Root Cause
The usage object returned by
extractFromTranscript()hardcodesreasoning_output_tokensto 0:The function parses Claude Code transcript JSON lines and aggregates
input_tokens,cache_creation_input_tokens,cache_read_input_tokens, andoutput_tokensfrom assistant messages — but never readsreasoning_output_tokens(also known asthinking_tokensorreasoning_tokensin the API response).Claude Code with extended thinking models (Opus 4 Thinking, etc.) DOES include this field in the transcript. For example, an assistant message from a thinking model contains:
{ "type": "assistant", "message": { "usage": { "input_tokens": 1234, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 500, "output_tokens": 800, "reasoning_output_tokens": 12000 } } }The 12,000 reasoning tokens are silently dropped.
Impact
Suggested Fix
In the assistant message processing loop, extract
reasoning_output_tokensfrom the usage block:Then in the return statement, use the tracked value instead of the hardcoded 0:
The
total_tokenscomputation on the line above may also need updating to include reasoning tokens (currently it isinput_tokens + cache_creation_input_tokens + output_tokens), depending on whether reasoning tokens are meant to be counted as new work tokens.