{"record":{"id":"9b858ec5f3489dda","repo":"siyuan-note/siyuan","slug":"encode-session-data-failed-w","errorCode":null,"errorMessage":"encode session data failed: %w","messagePattern":"encode session data failed: %w","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"kernel/agent/session.go","lineNumber":411,"sourceCode":"\t\t\tif !isRuntimeTurnTerminal(runtime.ActiveTurn) {\n\t\t\t\treturn currentRevision, nil, ErrRuntimeNotFinalized\n\t\t\t}\n\t\t\tif err := applyRuntimeTurnToSessionLocked(newData, runtime.ActiveTurn); err != nil {\n\t\t\t\treturn currentRevision, nil, err\n\t\t\t}\n\t\t} else if currentCommittedTurnID != commitTurnID {\n\t\t\treturn currentRevision, nil, ErrSessionConflict\n\t\t}\n\t}\n\n\tnewRevision := currentRevision + 1\n\tnewData[\"revision\"] = newRevision\n\tif commitTurnID != \"\" {\n\t\tnewData[\"lastCommittedTurnID\"] = commitTurnID\n\t}\n\tdata, err = gulu.JSON.MarshalIndentJSON(newData, \"\", \"\\t\")\n\tif err != nil {\n\t\treturn currentRevision, nil, fmt.Errorf(\"encode session data failed: %w\", err)\n\t}\n\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn currentRevision, nil, fmt.Errorf(\"create session dir failed: %w\", err)\n\t}\n\tif err := filelock.WriteFile(path, data); err != nil {\n\t\treturn currentRevision, nil, fmt.Errorf(\"save session file failed: %w\", err)\n\t}\n\tif commitTurnID != \"\" {\n\t\tif err := markRuntimeCommittedLocked(meta.ID, commitTurnID); err != nil {\n\t\t\tlogging.LogWarnf(\"commit agent runtime failed: %s\", err)\n\t\t}\n\t}\n\n\ttitle, _ := newData[\"title\"].(string)\n\tif title == \"\" {\n\t\ttitle = \"AI Agent\"\n\t}","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/agent/session.go#L393-L429","documentation":"The final serialization step of SaveSessionState: the merged session map (client payload + preserved unknown fields from disk + new revision) is written via gulu.JSON.MarshalIndentJSON (kernel/agent/session.go:409-411). If any value in that map cannot be represented in JSON, the save fails with 'encode session data failed' before anything is written — the on-disk file stays untouched.","triggerScenarios":"SaveSession called with a payload containing non-marshalable values — NaN/Inf numbers, or Go chan/func/complex values from in-process callers. Pure-JSON HTTP payloads decoded into map[string]any practically cannot trigger this; it guards programmatic callers and future field types.","commonSituations":"Kernel/plugin code building the session map in Go instead of round-tripping JSON; float NaN produced by arithmetic on client-side and smuggled in as a string then re-typed; essentially a canary for invariant breakage rather than a user-facing condition.","solutions":["Sanitize the payload to pure JSON types (string, number within ±1.7e308, bool, nil, slices, maps) before calling SaveSession from Go","Replace NaN/Inf with null or clamped values client-side","Log the failing map and bisect which key holds the unrepresentable value","No disk state was modified — after fixing the value, the same save can be retried safely"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// For Go callers: ensure every value in the map is JSON-representable\nfunc jsonSafe(v any) bool { _, err := json.Marshal(v); return err == nil }","typeGuard":"null","tryCatchPattern":"catch (e) {\n  if (/encode session data failed/.test(e?.data?.msg ?? '')) {\n    // sanitize payload to plain JSON types and retry; disk state is untouched\n  }\n}","preventionTips":["Build session payloads via JSON round-trips, not by injecting live Go values","Replace NaN/Inf with null before saving"],"tags":["agent","session","json","serialization"],"backgroundTag":"json-serialization-failed","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}