siyuan-note/siyuan · error

invalid session data

Error message

invalid session data

What it means

SaveSessionState first decodes the payload's metadata (sessionMeta). If the JSON cannot be unmarshalled, or meta.ID is empty or fails isValidSessionID, the whole payload is deemed unusable and it returns "invalid session data" without creating locks, directories, or files.

Solutions

  1. Ensure the payload is valid JSON and includes a non-empty, well-formed id field
  2. Validate the id client-side with the same charset rules before sending
  3. Log/inspect the raw request body when this fires — it usually means the sender serialized the wrong object
  4. Update the client after any schema change to sessionMeta so field names still match

Example fix

// before
body := map[string]any{"entries": entries} // id missing
SaveSessionState(marshal(body)) // invalid session data
// after
body := map[string]any{"id": sessionID, "entries": entries}
if sessionID == "" || !isValidSessionID(sessionID) {
    return errors.New("refusing to save: bad id")
}
SaveSessionState(marshal(body))
Defensive patterns

Strategy: validation

Validate before calling

var meta struct{ ID string `json:"id"` }
if json.Unmarshal(payload, &meta) != nil || meta.ID == "" || !isValidSessionID(meta.ID) { return errors.New("payload missing valid id") }

Type guard

func payloadHasValidID(payload []byte) bool {
    var m struct{ ID string `json:"id"` }
    return json.Unmarshal(payload, &m) == nil && m.ID != "" && isValidSessionID(m.ID)
}

Try / catch

if _, _, err := SaveSessionState(data); err != nil && strings.Contains(err.Error(), "invalid session data") {
    // fix payload: ensure valid JSON and a well-formed id, then resend
}

Prevention

When it happens

Trigger: Posting session data that is not valid JSON; JSON lacking the required id field; an id present but malformed (path separators, '..', illegal characters); wrong Content-Type leading to a non-JSON body reaching the parser.

Common situations: Client sends a partial/updated session object that dropped the id field; encoding layer sends form data instead of JSON; hand-crafted API calls with a typo'd or fabricated id.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/92ecde7b8f77fbf3. Report an issue: GitHub.

Appendix: source

Thrown at kernel/agent/session.go:323

		}
	}
	permissionMode, err := resolveSessionPermissionModeLocked(id, session)
	if err != nil {
		return nil, err
	}
	session["permissionMode"] = permissionMode
	return session, nil
}

func SaveSession(data []byte) (int64, error) {
	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")

View on GitHub (pinned to 9f775e8a12)