@@ -369,3 +369,54 @@ func TestBillingExactOvershootIsClamped(t *testing.T) {
369369 read , write , fresh , output , wantRead , wantFresh , wantOut , rows )
370370 }
371371}
372+
373+ // Claude Code writes locally fabricated assistant entries (model
374+ // "<synthetic>", isApiErrorMessage) with an all-zero usage object when an
375+ // API call errors. They were never billed: treating one as a real call
376+ // reconciles the whole history against a zero-token prompt and dumps the
377+ // usage-derived pieces into the fresh-input tier.
378+ func TestBillingSkipsSyntheticZeroUsageCalls (t * testing.T ) {
379+ u1 := & Usage {InputTokens : 100 , CacheCreationInputTokens : 8_000 , OutputTokens : 60_000 }
380+ entries := []TranscriptEntry {userPromptEntry ("do the thing" )}
381+ entries = append (entries , assistantCall (t , "m1" , u1 ,
382+ Content {Type : "thinking" , Thinking : "redacted" },
383+ Content {Type : "text" , Text : "working on it" },
384+ )... )
385+
386+ // The synthetic error entry: zero usage, full history would be "its
387+ // request" — must be ignored entirely.
388+ entries = append (entries , assistantCall (t , "synthetic-1" , & Usage {},
389+ Content {Type : "text" , Text : "API error: request interrupted" },
390+ )... )
391+
392+ u2 := & Usage {InputTokens : 50 , CacheReadInputTokens : 70_000 ,
393+ CacheCreationInputTokens : 2_000 , OutputTokens : 40 }
394+ entries = append (entries , assistantCall (t , "m2" , u2 , Content {Type : "text" , Text : "done" })... )
395+
396+ snap := computeBillingSnapshot (entries , entries )
397+ if snap == nil {
398+ t .Fatal ("expected billing snapshot" )
399+ }
400+ if got := snap ["llm_calls" ].(int ); got != 2 {
401+ t .Fatalf ("llm_calls = %d, want 2 (synthetic call must be skipped)" , got )
402+ }
403+
404+ wantRead := u1 .CacheReadInputTokens + u2 .CacheReadInputTokens
405+ wantWrite := u1 .CacheCreationInputTokens + u2 .CacheCreationInputTokens
406+ wantFresh := u1 .InputTokens + u2 .InputTokens
407+ wantOut := u1 .OutputTokens + u2 .OutputTokens
408+
409+ read , write , fresh , output , rows := billingColumnSums (snap )
410+ closeEnough := func (got , want int ) bool {
411+ d := got - want
412+ if d < 0 {
413+ d = - d
414+ }
415+ return d <= rows
416+ }
417+ if ! closeEnough (read , wantRead ) || ! closeEnough (write , wantWrite ) ||
418+ ! closeEnough (fresh , wantFresh ) || ! closeEnough (output , wantOut ) {
419+ t .Errorf ("Σ lanes = read %d / write %d / fresh %d / output %d, want %d/%d/%d/%d (±%d)" ,
420+ read , write , fresh , output , wantRead , wantWrite , wantFresh , wantOut , rows )
421+ }
422+ }
0 commit comments