siyuan-note/siyuan · error

invalid session data

Error message

invalid session data

What it means

SaveSessionState (kernel/agent/session.go:319) first decodes the posted body into sessionMeta; if that decode fails, or the top-level id field is empty / not a 20-char [0-9a-z] block ID, the whole payload is rejected as 'invalid session data'. This is the entry gate for POST /api/ai/agent/saveSession — nothing is read or written on disk.

Source

Thrown at kernel/agent/session.go:322

		}
	}
	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 afa823b6b4)

Solutions

  1. Send the exact session object previously returned by GET /api/ai/agent/getSession, with its id untouched
  2. Verify JSON.stringify(payload) round-trips and payload.id matches /^[0-9a-z]{20}$/ before posting
  3. If creating a brand-new session, generate a 20-char [0-9a-z] id once and reuse it
  4. Do not retry unchanged — fix the payload shape first

Example fix

// before: nested envelope, id not top-level
fetchPost('/api/ai/agent/saveSession', {data: session});

// after: flat session object with canonical id
if (/^[0-9a-z]{20}$/.test(session.id)) {
  await fetchPost('/api/ai/agent/saveSession', session);
}
Defensive patterns

Strategy: validation

Validate before calling

const ok = payload && typeof payload === 'object' &&
  !Array.isArray(payload) &&
  /^[0-9a-z]{20}$/.test(payload.id ?? '');
if (!ok) throw new Error('invalid session data');

Type guard

const isSessionPayload = (p: unknown): p is {id: string; [k: string]: any} =>
  !!p && typeof p === 'object' && !Array.isArray(p) &&
  /^[0-9a-z]{20}$/.test((p as any).id);

Try / catch

null

Prevention

When it happens

Trigger: POST /api/ai/agent/saveSession whose body is not a JSON object (array, string, truncated JSON), has no id field, or has an id like "session-1" / 22 chars / uppercase. Note the id must sit at the top level of the body, not nested under data or session.

Common situations: Client wraps the session in an envelope ({data: {...}}); sends the runtime/chat payload instead of the session snapshot; body truncated by a proxy or fetch timeout; id generated client-side instead of taken from getSession.

Related errors


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