JuliusBrussee/caveman · error
usage export contained no priced rows
Error message
usage export contained no priced rows
What it means
parseUsageExport reads a provider usage-export CSV and accumulates billed token totals per model. After the header maps successfully, each row only counts when it has a non-empty model cell AND a positive sum of input+output+cache_read+cache_write tokens (reconcileCell silently returns 0 for blank, non-numeric, or negative cells). If the loop finishes and not a single row qualified, the map is empty and the function fails closed with 'usage export contained no priced rows' rather than reconciling against zero. This guards BuildLearnReconcile from producing a meaningless 100%-unattributed reconciliation.
Source
Thrown at proxy/internal/store/learn_reconcile.go:177
modelIndex := columns["model"]
if modelIndex >= len(record) {
continue
}
model := strings.TrimSpace(record[modelIndex])
if model == "" {
continue
}
total := reconcileCell(record, columns, "input") +
reconcileCell(record, columns, "output") +
reconcileCell(record, columns, "cache_read") +
reconcileCell(record, columns, "cache_write")
if total <= 0 {
continue
}
billed[model] += total
}
if len(billed) == 0 {
return nil, fmt.Errorf("usage export contained no priced rows")
}
return billed, nil
}
// BuildLearnReconcile compares an export against the scanned window.
func (s *Store) BuildLearnReconcile(cwd, exportPath string, sources []string, sinceExpr string) (LearnReconcile, error) {
billed, err := parseUsageExport(exportPath)
if err != nil {
return LearnReconcile{}, err
}
plan, err := s.BuildLearnPlan(cwd, sources, sinceExpr)
if err != nil {
return LearnReconcile{}, err
}
return buildLearnReconcile(billed, plan.Spend, exportPath), nil
}
func buildLearnReconcile(billed map[string]int64, spend *LearnSpend, source string) LearnReconcile {View on GitHub (pinned to 81536f57b3)
Solutions
- Open the CSV and confirm it has data rows with a non-empty model column and at least one populated token column (input/output/cache_read/cache_write) per row
- Check you exported the token usage report for the same time window as the reconcile scan — an empty window yields zero qualifying rows
- If token cells contain formatted numbers (e.g. '1,234.0' works, but '$12' or '—' does not), clean them so they parse as plain floats, since non-numeric cells silently count as 0
- If the provider renamed columns, update mapReconcileHeader to recognize the new header variants (see how TestReconcileAcceptsProviderHeaderVariants pins this)
- As a last resort, regenerate the export from the provider and rerun BuildLearnReconcile
Example fix
// before: exporting a spend-only CSV // model,input,output,cost // claude-sonnet-4,,,12.50 // after: export the token usage report // model,input,output,cache_read,cache_write // claude-sonnet-4,1024,2048,0,0
Defensive patterns
Strategy: validation
Validate before calling
func hasPricedRows(path string) (bool, error) {
f, err := os.Open(path)
if err != nil {
return false, err
}
defer f.Close()
r := csv.NewReader(f)
r.FieldsPerRecord = -1
header, err := r.Read()
if err != nil {
return false, fmt.Errorf("no header row: %w", err)
}
cols, err := mapReconcileHeader(header) // same mapping the parser uses
if err != nil {
return false, err
}
mi := cols["model"]
for {
rec, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
return false, err
}
if mi < len(rec) && strings.TrimSpace(rec[mi]) != "" {
total := reconcileCell(rec, cols, "input") + reconcileCell(rec, cols, "output") +
reconcileCell(rec, cols, "cache_read") + reconcileCell(rec, cols, "cache_write")
if total > 0 {
return true, nil
}
}
}
return false, nil
}
// before calling:
// ok, err := hasPricedRows(exportPath)
// if err != nil { return err }
// if !ok { return errors.New("export has no priced rows; re-download the token usage report") } Type guard
//go:build go
// not a type error — guard the error value instead:
func isNoPricedRowsErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "usage export contained no priced rows")
} Try / catch
reconcile, err := store.BuildLearnReconcile(cwd, exportPath, sources, sinceExpr)
if err != nil {
if isNoPricedRowsErr(err) {
// treat as bad/incomplete export input: surface an actionable message,
// do NOT fall back to a zero-billed reconciliation
log.Printf("%s: export %s has headers but no rows with a model and positive tokens; re-export the token usage report", err, exportPath)
return err
}
return fmt.Errorf("reconcile: %w", err)
} Prevention
- Always download the token-usage CSV (with input/output/cache columns), not a dollars-only invoice export
- Verify the export's date range overlaps the sinceExpr window used for the scan before reconciling
- Sanity-check the CSV in a spreadsheet: at least one row with a model name and non-zero token counts
- Automate a pre-flight row count check (data rows > 0) in pipelines that feed BuildLearnReconcile
- When a provider changes its export schema, extend mapReconcileHeader and add a header-variant test rather than editing the CSV by hand
When it happens
Trigger: Calling Store.BuildLearnReconcile(cwd, exportPath, sources, sinceExpr) with a CSV whose header maps (it has recognizable token columns) but where every data row is skipped: model cell blank, model column index beyond the row length, all token cells empty, token cells containing non-numeric text (currency symbols, 'N/A'), or token values that parse to 0/negative. Also triggered by a header-only CSV with no data rows, or by pointing exportPath at a cost-only/dollars-only export that lacks populated token columns.
Common situations: Downloading the wrong export artifact (an invoice or spend summary instead of the token usage CSV); exporting from a provider console with a date filter that excludes all metered traffic; a schema change where the provider renames or reformats token columns so reconcileCell reads empty cells; hand-edited CSVs where commas were used as thousands separators inconsistently or values were replaced with dashes; passing a template/example CSV that has headers but no rows.
Related errors
- option not found
- rewriter: model is required
- rewriter: api key is required
- kms: auth token is required
- kms: invalid API base URL
AI-assisted analysis of JuliusBrussee/caveman@81536f57b3 (2026-08-27).
Data as JSON: /api/errors/d5b3571c34938f62.
Report an issue: GitHub.