sipeed/picoclaw · error

marshal result: %w

Error message

marshal result: %w

What it means

json.MarshalIndent failure inside SaveResults (cmd/membench/eval.go). Each EvalResult must serialize to JSON; Go's encoding/json errors on unsupported values — NaN/Inf floats, channels, funcs, or cycles. Given EvalResult carries numeric metrics, the realistic cause is a NaN score produced when a metric divides by zero (e.g. F1 with no true positives and no predictions).

Source

Thrown at cmd/membench/eval.go:275

	return AggMetrics{
		OverallF1:      overallF1,
		OverallHitRate: totalHitRate / float64(nHit),
		ByCategory:     byCat,
		TotalQuestions: len(qaResults),
		ValidF1Count:   validF1Count,
	}
}

// SaveResults writes per-sample eval results to JSON files.
func SaveResults(results []EvalResult, outDir string) error {
	if err := os.MkdirAll(outDir, 0o755); err != nil {
		return fmt.Errorf("create output dir: %w", err)
	}
	for _, r := range results {
		path := filepath.Join(outDir, fmt.Sprintf("eval_%s_%s.json", r.Mode, r.SampleID))
		data, err := json.MarshalIndent(r, "", "  ")
		if err != nil {
			return fmt.Errorf("marshal result: %w", err)
		}
		if err := os.WriteFile(path, data, 0o644); err != nil {
			return fmt.Errorf("write result: %w", err)
		}
	}
	return nil
}

// SaveAggregated writes a combined results.json with all modes.
func SaveAggregated(results []EvalResult, outDir string) error {
	byMode := map[string][]EvalResult{}
	for _, r := range results {
		byMode[r.Mode] = append(byMode[r.Mode], r)
	}

	aggMap := map[string]AggMetrics{}
	for mode, modeResults := range byMode {
		aggMap[mode] = computeModeAgg(modeResults)

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Sanitize metrics before saving: replace NaN/Inf with 0 (math.IsNaN/math.IsInf)
  2. Fix the root cause: guard division so F1 is 0 when denominator is 0 instead of NaN
  3. Inspect the failing EvalResult by logging r.SampleID/r.Mode before Marshal
  4. Never add func/chan-typed fields to structs passed to MarshalIndent

Example fix

// before
byCat[cat] = f1 // f1 may be NaN (0/0)

// after
if math.IsNaN(f1) || math.IsInf(f1, 0) {
    f1 = 0
}
byCat[cat] = f1
Defensive patterns

Strategy: validation

Validate before calling

func sanitizeFloats(m AggMetrics) AggMetrics {
    fix := func(v float64) float64 {
        if math.IsNaN(v) || math.IsInf(v, 0) {
            return 0
        }
        return v
    }
    m.OverallF1 = fix(m.OverallF1)
    m.OverallHitRate = fix(m.OverallHitRate)
    for k, v := range m.ByCategory {
        v.F1 = fix(v.F1)
        m.ByCategory[k] = v
    }
    return m
}

Try / catch

if data, err := json.MarshalIndent(r, "", "  "); err != nil {
    return fmt.Errorf("marshal result for %s/%s (likely NaN metric): %w", r.Mode, r.SampleID, err)
}

Prevention

When it happens

Trigger: A question category where every sample yields NaN F1 (precision/recall both 0/0) and that NaN reaches the struct; adding a new field of type chan or func to EvalResult; Inf token counts from a parsing bug.

Common situations: Eval runs where a mode answers nothing for a category; upstream schema change adding non-serializable fields; corpus edge case producing 0/0 division.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/d432ac0b288db314. Report an issue: GitHub.