Skip to content

fix(claude_code): measure per-tool latency, which was always reported as 0 - #601

Open
omkargaikwad23 wants to merge 4 commits into
mainfrom
fix/claude-code-tool-latency
Open

fix(claude_code): measure per-tool latency, which was always reported as 0#601
omkargaikwad23 wants to merge 4 commits into
mainfrom
fix/claude-code-tool-latency

Conversation

@omkargaikwad23

@omkargaikwad23 omkargaikwad23 commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Issue found during harness CI check work: logs

tool_call_latency scored 0 for every Claude Code scenario. tools.durationMs and totalDurationMs were initialized to 0 in _parse_stream_json and never accumulated — and there was nothing to accumulate from: Claude's stream-json carries no timestamps, and the CLI ran to completion before parsing, so nothing measured the gap.

Fix: stream stdout line-by-line, stamp time.monotonic() when a tool_use arrives and again at its matching tool_result, then accumulate into the tool stats.

Also guards the parser against a malformed line: _stamp_tool_event assumed every decoded line was an object, so a line parsing to a list raised AttributeError, broke out of the stdout drain loop, and discarded the rest of the agent's response.

Notes for reviewers

  • _execute_cli_streaming mirrors codex_cli._execute_cli_command verbatim. Hoisting both into AgentCliGenerator is the right end state, left as a follow-up to keep this bugfix reviewable. codex_cli also has the same AttributeError hole.
  • _execute_cli_command is unchanged — its two setup callers still expect a bare CompletedProcess.
  • Parallel calls share a start time, so totalDurationMs sums tool time rather than wall-clock. That matches the other generators and how the scorer already aggregates.

Test plan

  • pytest evalbench/test/claude_code_test.py — 7 passed, up from 5
  • Covers the regression itself, both tool_result shapes, parallel calls, and malformed lines
  • Full suite: no new failures
  • Confirm tool_call_latency > 0 for claude_code in the next harness CI run

… as 0

Claude Code's stream-json carries no timestamps on tool_use or tool_result
events and no duration field, and the CLI was run to completion before its
output was parsed. tools.durationMs and tools.totalDurationMs were therefore
initialized to 0 and never accumulated, so tool_call_latency scored 0 for
every scenario.

Stream stdout line-by-line and stamp the arrival time of each tool_use block
and its matching tool_result, then accumulate the gap into the tool stats.
This mirrors what codex_cli already does; gemini_cli gets duration_ms from
the CLI itself.
…onse

_stamp_tool_event assumed every parsed JSON line was an object, so a line
decoding to a list raised AttributeError. That propagated into the stdout
drain loop and broke out of it, discarding every remaining line -- losing the
agent's response to salvage a timing number.

Guard the parse and isolate the stamping so timing can never cost us stdout.
Also flatten the tool_result id handling and cover the top-level tool_result
shape, parallel calls, and malformed lines.
The 'stream-json carries no timestamps' rationale was stated in three
places. Keep it once, on the method whose existence it justifies, and cut
the prose that restated what the code already shows.
test_parse_stream_json_without_durations fed a stream with no tool calls,
so the accumulation loop never ran and it only asserted totalDurationMs was
still 0 -- which passes just as well against the bug it was meant to guard.
@prernakakkar-google

Copy link
Copy Markdown
Collaborator

/gcbrun

@prernakakkar-google

Copy link
Copy Markdown
Collaborator

WHy verify-harness is showing up

@prernakakkar-google

Copy link
Copy Markdown
Collaborator

Parallel tool calls double-count into totalDurationMs

@omkargaikwad23

Copy link
Copy Markdown
Collaborator Author

WHy verify-harness is showing up

I have added the PR trigger in gcp project. We will be releasing that feature soon.

@omkargaikwad23

Copy link
Copy Markdown
Collaborator Author

Parallel tool calls double-count into totalDurationMs

Good catch. Parallel calls do sum rather than merge. That's the existing convention in all four generators, and toolcalllatency.py:70 sums across turns on top of that, so the metric is cumulative tool work rather than elapsed time. Changing it here alone would make Claude the only harness reporting a different quantity. Happy to follow up with a change across all four if we want wall-clock semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants