alibaba/open-code-review · error
marshal session_end: %w
Error message
marshal session_end: %w
What it means
WriteSessionEnd marshals the final session_end record explicitly so a JSON marshal failure on the closing record is reported rather than swallowed. Before returning the error it flushes the buffered writer and closes the file, leaving the session file without its session_end record. json.Marshal of a map of plain values should not fail in practice, so this usually indicates a custom/unsupported value (e.g. NaN/Inf float, channel, func) was injected into the record fields.
Source
Thrown at internal/session/persist.go:399
"duration_seconds": duration.Seconds(),
"llm_failures": llmFailures,
}
if manifest != nil {
rec["run_manifest"] = manifest
}
jw.lastUUID = uuid
// Marshal explicitly (not via writeRecordLocked) so a marshal failure on the
// final record is reported rather than swallowed.
data, err := json.Marshal(rec)
if err != nil {
if jw.writer != nil {
jw.writer.Flush()
}
if jw.file != nil {
jw.file.Close()
}
return fmt.Errorf("marshal session_end: %w", err)
}
var writeErr error
if jw.writer != nil {
if _, err := jw.writer.Write(data); err != nil {
writeErr = fmt.Errorf("write session_end: %w", err)
} else if err := jw.writer.WriteByte('\n'); err != nil {
writeErr = fmt.Errorf("write session_end: %w", err)
} else if err := jw.writer.Flush(); err != nil {
writeErr = fmt.Errorf("flush session_end: %w", err)
}
}
if jw.file != nil {
if err := jw.file.Close(); err != nil && writeErr == nil {
writeErr = fmt.Errorf("close session file: %w", err)
}
}
return writeErrView on GitHub (pinned to 5cf97d0d15)
Solutions
- Inspect the wrapped error for the offending Go type ('json: unsupported type: ...') and fix the value passed into WriteSessionEnd/RunManifest
- Sanitize floats (reject/convert NaN and +Inf) before adding them to the manifest
- Add MarshalJSON methods or change fields to marshalable types in RunManifest
- Note the file was already flushed and closed by the error path — do not reuse the writer afterward; create a new session
Example fix
// before
rec["score"] = math.NaN()
// after
if math.IsNaN(score) { score = 0 }
rec["score"] = score Defensive patterns
Strategy: try-catch
Validate before calling
// pre-validate manifest values are marshalable
if _, err := json.Marshal(manifest); err != nil {
return fmt.Errorf("run manifest not marshalable: %w", err)
} Try / catch
if err := writer.WriteSessionEnd(); err != nil {
if strings.Contains(err.Error(), "marshal session_end") {
log.Printf("session_end record not written; file already closed: %v", err)
// do not reuse this writer; start a new session
}
} Prevention
- Never put NaN/Inf floats, funcs, or channels into run manifest fields
- Test WriteSessionEnd once with a full manifest in unit tests
- Remember the error path closes the file: treat the session as finalized either way
When it happens
Trigger: WriteSessionEnd builds the rec map (uuid, counts, files_reviewed, duration_seconds, llm_failures, optional run_manifest) and json.Marshal(rec) returns an error; WriteSessionEnd is the last call before the session file is finalized.
Common situations: Passing a RunManifest containing non-marshalable values (func, channel, or invalid float like NaN in custom metrics); future code changes adding unsupported types to the record.
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
- marshal config: %w
- invalid tool call arguments for %s: %w
- parse config: %w
- parse app config: %w
- invalid JSON for llm.extra_body: %w
AI-assisted analysis of alibaba/open-code-review@5cf97d0d15 (2026-09-02).
Data as JSON: /api/errors/2038921a81107b2a.
Report an issue: GitHub.