siyuan-note/siyuan · error

encode session data failed: %w

Error message

encode session data failed: %w

What it means

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.

Source

Thrown at kernel/agent/session.go:411

			if !isRuntimeTurnTerminal(runtime.ActiveTurn) {
				return currentRevision, nil, ErrRuntimeNotFinalized
			}
			if err := applyRuntimeTurnToSessionLocked(newData, runtime.ActiveTurn); err != nil {
				return currentRevision, nil, err
			}
		} else if currentCommittedTurnID != commitTurnID {
			return currentRevision, nil, ErrSessionConflict
		}
	}

	newRevision := currentRevision + 1
	newData["revision"] = newRevision
	if commitTurnID != "" {
		newData["lastCommittedTurnID"] = commitTurnID
	}
	data, err = gulu.JSON.MarshalIndentJSON(newData, "", "\t")
	if err != nil {
		return currentRevision, nil, fmt.Errorf("encode session data failed: %w", err)
	}

	if err := os.MkdirAll(dir, 0755); err != nil {
		return currentRevision, nil, fmt.Errorf("create session dir failed: %w", err)
	}
	if err := filelock.WriteFile(path, data); err != nil {
		return currentRevision, nil, fmt.Errorf("save session file failed: %w", err)
	}
	if commitTurnID != "" {
		if err := markRuntimeCommittedLocked(meta.ID, commitTurnID); err != nil {
			logging.LogWarnf("commit agent runtime failed: %s", err)
		}
	}

	title, _ := newData["title"].(string)
	if title == "" {
		title = "AI Agent"
	}

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Sanitize the payload to pure JSON types (string, number within ±1.7e308, bool, nil, slices, maps) before calling SaveSession from Go
  2. Replace NaN/Inf with null or clamped values client-side
  3. Log the failing map and bisect which key holds the unrepresentable value
  4. No disk state was modified — after fixing the value, the same save can be retried safely
Defensive patterns

Strategy: try-catch

Validate before calling

// For Go callers: ensure every value in the map is JSON-representable
func jsonSafe(v any) bool { _, err := json.Marshal(v); return err == nil }

Type guard

null

Try / catch

catch (e) {
  if (/encode session data failed/.test(e?.data?.msg ?? '')) {
    // sanitize payload to plain JSON types and retry; disk state is untouched
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/9b858ec5f3489dda. Report an issue: GitHub.