siyuan-note/siyuan · error
decode existing session data failed
Error message
decode existing session data failed: %w
What it means
When saving, SaveSessionState reads the existing session.json to compare revisions and committed turn ids. If that on-disk file exists but cannot be parsed as JSON, it fails closed with "decode existing session data failed: %w" instead of treating the store as empty — overwriting would destroy the old revision history and break commit idempotency.
Solutions
- Inspect the wrapped error and the corrupt session.json at data/<sessionsDir>/<id>/session.json
- Restore the file from backup or sync history, then retry the save
- If unrecoverable, move the corrupt file aside and rebuild the session (accepting history loss for that session)
- Fix the writer to save atomically (write temp file + rename) to prevent recurrence
Defensive patterns
Strategy: fallback
Validate before calling
raw, err := os.ReadFile(sessionPath)
if err == nil && len(raw) > 0 { var probe map[string]any; if json.Unmarshal(raw, &probe) != nil { /* corrupt: restore from backup before saving */ } } Type guard
func existingSessionIsParseable(path string) bool {
raw, err := os.ReadFile(path)
if err != nil || len(raw) == 0 { return true }
var m map[string]any
return json.Unmarshal(raw, &m) == nil
} Try / catch
if _, _, err := SaveSessionState(data); err != nil && strings.HasPrefix(err.Error(), "decode existing session data failed") {
// restore session.json from backup/sync history, or quarantine it, then retry
} Prevention
- Make the kernel's session writes atomic (temp file + rename) to avoid truncation
- Do not hand-edit files under the sessions directory
- Keep backups/sync history for the AI sessions directory
- Monitor disk-full conditions on the workspace volume
When it happens
Trigger: session.json on disk is truncated (crash mid-write, disk full), corrupted by an external process, or written by an incompatible older/newer format that no longer parses.
Common situations: Kernel killed during a previous save without atomic rename; manual edits to data/ai session files; restoring partial backups; disk-full leaving zero-length or partial files.
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 session data failed
- invalid session data
- agent runtime session id mismatch
- agent runtime turn changed
- agent runtime turn is not finalized
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/091d88f4331a3d78.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/agent/session.go:354
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]any
if err := gulu.JSON.UnmarshalJSON(existing, &existingData); err != nil {
return 0, nil, fmt.Errorf("decode existing session data failed: %w", err)
} else {
currentRevision = numberToInt64(existingData["revision"])
currentCommittedTurnID, _ = existingData["lastCommittedTurnID"].(string)
if commitTurnID != "" && currentCommittedTurnID == commitTurnID {
// 提交响应丢失后,客户端可能原样重试同一个 commitTurnID。此判断要先于修订号校验,
// 并且不能再用客户端快照覆盖已经由 runtime 生成的权威内容。
if err := markRuntimeCommittedLocked(meta.ID, commitTurnID); err != nil {
logging.LogWarnf("clean committed agent runtime failed: %s", err)
}
return currentRevision, existingData, nil
}
if meta.ExpectedRevision != nil && *meta.ExpectedRevision != currentRevision {
return currentRevision, nil, ErrSessionConflict
}
for k, v := range existingData {
if _, ok := newData[k]; !ok {
// messages 是已废弃的旧会话字段,不再带入新格式;其他未知字段原样保留,
// 避免前后端版本不一致时擦除较新版本写入的数据。View on GitHub (pinned to 9f775e8a12)