siyuan-note/siyuan · error
decode session data failed
Error message
decode session data failed: %w
What it means
After metadata validation, SaveSessionState decodes the full payload into a generic map. If that second gulu.JSON.UnmarshalJSON fails, it wraps the underlying error as "decode session data failed: %w". This catches payloads that parse for sessionMeta but are not valid JSON overall, or contain structurally incompatible values for the full decode pass.
Solutions
- Inspect the wrapped inner error (%w) to find the exact JSON syntax offset
- Re-send the full, complete session JSON from the client
- Fix the client serializer to emit strict JSON (no NaN, no trailing commas)
- Validate the payload with a strict JSON parser client-side before sending
Example fix
// before
payload := fmt.Sprintf("{"id":%q,,"entries":%v}", id, entries) // malformed
SaveSessionState([]byte(payload))
// after
payload, err := json.Marshal(sessionObj)
if err != nil { return err }
SaveSessionState(payload) Defensive patterns
Strategy: try-catch
Validate before calling
var probe any
if err := json.Unmarshal(payload, &probe); err != nil { /* fix payload before sending */ } Try / catch
if _, _, err := SaveSessionState(data); err != nil && strings.HasPrefix(err.Error(), "decode session data failed") {
var perr *json.SyntaxError
if errors.As(err, &perr) { log.Printf("bad JSON at offset %d", perr.Offset) }
} Prevention
- Marshal with a strict JSON encoder; never concatenate JSON fragments
- Ensure uploads complete fully before save (no truncated bodies)
- Test payloads containing special floats/unicode for encoder strictness
When it happens
Trigger: Sending truncated or syntactically invalid JSON (metadata parse succeeded earlier only if the error appeared later in the body — otherwise see error 107); duplicating or corrupting bytes during transport; a custom encoder emitting invalid JSON (NaN, trailing commas).
Common situations: Interrupted uploads leaving partial bodies; proxy/encoding layers mangling the payload; client bug writing NaN/Infinity into numeric fields.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- decode existing session data failed
- invalid session data
- agent runtime turn changed
- agent runtime turn is not finalized
- agent session revision conflict
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/ea1696ccf17c89a8.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/agent/session.go:334
revision, _, err := SaveSessionState(data)
return revision, err
}
func SaveSessionState(data []byte) (int64, map[string]any, error) {
var meta sessionMeta
if err := gulu.JSON.UnmarshalJSON(data, &meta); err != nil || meta.ID == "" || !isValidSessionID(meta.ID) {
return 0, nil, fmt.Errorf("invalid session data")
}
lock := sessionLock(meta.ID)
lock.Lock()
defer lock.Unlock()
dir := filepath.Join(sessionsDir(), meta.ID)
path := filepath.Join(dir, "session.json")
var newData map[string]any
if err := gulu.JSON.UnmarshalJSON(data, &newData); err != nil {
return 0, nil, fmt.Errorf("decode session data failed: %w", err)
}
delete(newData, "expectedRevision")
delete(newData, "commitTurnID")
delete(newData, "recoveryTurnID")
delete(newData, "recoveryState")
delete(newData, "recoveryRevision")
delete(newData, "agentRunning")
delete(newData, "lastCommittedTurnID")
commitTurnID := meta.CommitTurnID
if commitTurnID == "" {
commitTurnID = meta.RecoveryTurnID
}
currentRevision := int64(0)
currentCommittedTurnID := ""
existing, err := os.ReadFile(path)
if err == nil && len(existing) > 0 {
var existingData map[string]anyView on GitHub (pinned to 9f775e8a12)