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

  1. 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
  2. Check you exported the token usage report for the same time window as the reconcile scan — an empty window yields zero qualifying rows
  3. 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
  4. If the provider renamed columns, update mapReconcileHeader to recognize the new header variants (see how TestReconcileAcceptsProviderHeaderVariants pins this)
  5. 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

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


AI-assisted analysis of JuliusBrussee/caveman@81536f57b3 (2026-08-27). Data as JSON: /api/errors/d5b3571c34938f62. Report an issue: GitHub.