File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -266,6 +266,28 @@ def test_non_numeric_usage_tokens_do_not_crash(tmp_path: Path) -> None:
266266 assert session ["metadata" ]["total_ephemeral_5m_tokens" ] == 0
267267
268268
269+ def test_negative_usage_tokens_clamp_to_zero (tmp_path : Path ) -> None :
270+ """Negative token counts must not reduce session metadata totals."""
271+ entry = {
272+ "type" : "assistant" ,
273+ "timestamp" : "2026-06-11T00:00:00Z" ,
274+ "message" : {
275+ "model" : "claude-test" ,
276+ "content" : [{"type" : "text" , "text" : "hi" }],
277+ "usage" : {
278+ "input_tokens" : - 100 ,
279+ "output_tokens" : - 1.5 ,
280+ "cache_creation" : {"ephemeral_5m_input_tokens" : - 50 },
281+ },
282+ },
283+ }
284+ path = _write_jsonl (tmp_path / "negative_usage.jsonl" , [json .dumps (entry )])
285+ session = parse_session (path )
286+ assert session ["metadata" ]["total_input_tokens" ] == 0
287+ assert session ["metadata" ]["total_output_tokens" ] == 0
288+ assert session ["metadata" ]["total_ephemeral_5m_tokens" ] == 0
289+
290+
269291def test_non_finite_usage_tokens_do_not_crash (tmp_path : Path ) -> None :
270292 """json.loads accepts NaN/Infinity literals; int(nan)/int(inf) raise, so the
271293 parser must coerce them to 0 rather than propagate ValueError/OverflowError."""
Original file line number Diff line number Diff line change 4242
4343
4444def _safe_int (val : Any ) -> int :
45- """Coerce a value to int for token accounting; non-numeric or non-finite
46- input becomes 0 so fuzzed/malformed usage fields never raise during
47- arithmetic. json.loads accepts NaN/Infinity literals, so guard against them ."""
45+ """Coerce a value to a non-negative int for token accounting; non-numeric,
46+ non-finite, or negative input becomes 0 so fuzzed/malformed usage fields
47+ never raise during arithmetic and counters cannot go below zero ."""
4848 if isinstance (val , bool ):
4949 return 0
5050 if isinstance (val , int ):
51- return val
51+ return max ( 0 , val )
5252 if isinstance (val , float ):
53- return int (val ) if math .isfinite (val ) else 0
53+ return max ( 0 , int (val ) ) if math .isfinite (val ) else 0
5454 return 0
5555
5656
You can’t perform that action at this time.
0 commit comments