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
- Sanitize metrics before saving: replace NaN/Inf with 0 (math.IsNaN/math.IsInf)
- Fix the root cause: guard division so F1 is 0 when denominator is 0 instead of NaN
- Inspect the failing EvalResult by logging r.SampleID/r.Mode before Marshal
- 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
- Guard every division: return 0 when the denominator is 0
- Never let NaN/Inf reach structs passed to encoding/json — it errors, doesn't encode null
- Keep only string/number/bool/slice/map fields in result structs
- Add a golden-file unit test that marshals a fully populated EvalResult
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
- create output dir: %w
- write result: %w
- create seahorse engine: %w
- ingest sample %s: %w
- get conversation for %s: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/d432ac0b288db314.
Report an issue: GitHub.