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
- Send the exact session object previously returned by GET /api/ai/agent/getSession, with its id untouched
- Verify JSON.stringify(payload) round-trips and payload.id matches /^[0-9a-z]{20}$/ before posting
- If creating a brand-new session, generate a 20-char [0-9a-z] id once and reuse it
- 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
- Post the exact object previously returned by getSession; do not wrap it in an envelope
- Keep the id at the top level of the body
- JSON round-trip the payload once before sending to catch truncation early
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
- invalid session id
- decode session data failed: %w
- agent session revision conflict
- decode existing session data failed: %w
- read session file failed: %w
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/92ecde7b8f77fbf3.
Report an issue: GitHub.