JuliusBrussee/caveman · error

encode stats report

Error message

encode stats report: %w

What it means

WriteStatsHTML serializes a StatsReport to JSON before rendering it to an HTML file. This error wraps any failure from json.Marshal of the report. It means the StatsReport itself could not be encoded, typically because it contains a value JSON cannot represent (unsupported types, cyclic references, or a custom MarshalJSON that failed).

Solutions

  1. Marshal the report yourself first (json.Marshal(report)) to see the underlying encoding error message.
  2. Audit the StatsReport fields added most recently for unsupported types or a failing MarshalJSON.
  3. Add json:"..." tags and ensure all map keys and field types are JSON-encodable (strings/ints/floats/bools/slices/nested structs).
  4. If a custom MarshalJSON exists on any report type, fix it to return valid JSON or nil error.

Example fix

// before
type StatsReport struct {
    Extra map[int]string // unsupported key type
}
// after
type StatsReport struct {
    Extra map[string]string `json:"extra,omitempty"`
}
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := json.Marshal(report); err != nil {
    // report is not encodable; fix fields before calling WriteStatsHTML
}

Try / catch

if err := s.WriteStatsHTML(report, outPath); err != nil {
    if strings.Contains(err.Error(), "encode stats report") {
        log.Printf("report not JSON-encodable: %v", err) // inspect wrapped cause
    }
}

Prevention

When it happens

Trigger: Calling Store.WriteStatsHTML(report, outPath) with a StatsReport containing a field that json.Marshal cannot encode — e.g. a channel, func, complex number, cyclic pointer graph, or a field whose MarshalJSON returns an error.

Common situations: Developers add a custom type to StatsReport with a buggy MarshalJSON, or stuff a non-serializable runtime value (map with non-string keys of unsupported kind, func) into a report field before calling WriteStatsHTML.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/b88c6717aa8430d8. Report an issue: GitHub.

Appendix: source

Thrown at proxy/internal/store/stats_report.go:23

	"fmt"
	"html/template"
	"os"
	"path/filepath"
)

func DefaultStatsReportPath(home string) string {
	return filepath.Join(home, "reports", "caveman-stats.html")
}

// WriteStatsHTML writes a private, self-contained snapshot. The page only rolls
// up recorded metric groups; it never derives a price or upgrades evidence.
func (s *Store) WriteStatsHTML(report StatsReport, outPath string) error {
	if outPath == "" {
		return fmt.Errorf("report path is required")
	}
	raw, err := json.Marshal(report)
	if err != nil {
		return fmt.Errorf("encode stats report: %w", err)
	}
	if err := os.MkdirAll(filepath.Dir(outPath), 0o700); err != nil {
		return err
	}
	f, err := os.CreateTemp(filepath.Dir(outPath), ".caveman-stats-*.html")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())
	// encoding/json escapes <, >, &, and JavaScript line separators. Mark only
	// its output as JavaScript: an untrusted model name cannot close the script.
	err = statsReportTemplate.Execute(f, struct{ Data template.JS }{Data: template.JS(raw)})
	if closeErr := f.Close(); err == nil {
		err = closeErr
	}
	if err != nil {
		return err
	}

View on GitHub (pinned to 3ee70a1026)