{"record":{"id":"92ecde7b8f77fbf3","repo":"siyuan-note/siyuan","slug":"invalid-session-data","errorCode":null,"errorMessage":"invalid session data","messagePattern":"invalid session data","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"kernel/agent/session.go","lineNumber":322,"sourceCode":"\t\t}\n\t}\n\tpermissionMode, err := resolveSessionPermissionModeLocked(id, session)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession[\"permissionMode\"] = permissionMode\n\treturn session, nil\n}\n\nfunc SaveSession(data []byte) (int64, error) {\n\trevision, _, err := SaveSessionState(data)\n\treturn revision, err\n}\n\nfunc SaveSessionState(data []byte) (int64, map[string]any, error) {\n\tvar meta sessionMeta\n\tif err := gulu.JSON.UnmarshalJSON(data, &meta); err != nil || meta.ID == \"\" || !isValidSessionID(meta.ID) {\n\t\treturn 0, nil, fmt.Errorf(\"invalid session data\")\n\t}\n\tlock := sessionLock(meta.ID)\n\tlock.Lock()\n\tdefer lock.Unlock()\n\n\tdir := filepath.Join(sessionsDir(), meta.ID)\n\tpath := filepath.Join(dir, \"session.json\")\n\n\tvar newData map[string]any\n\tif err := gulu.JSON.UnmarshalJSON(data, &newData); err != nil {\n\t\treturn 0, nil, fmt.Errorf(\"decode session data failed: %w\", err)\n\t}\n\tdelete(newData, \"expectedRevision\")\n\tdelete(newData, \"commitTurnID\")\n\tdelete(newData, \"recoveryTurnID\")\n\tdelete(newData, \"recoveryState\")\n\tdelete(newData, \"recoveryRevision\")\n\tdelete(newData, \"agentRunning\")","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/agent/session.go#L304-L340","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: nested envelope, id not top-level\nfetchPost('/api/ai/agent/saveSession', {data: session});\n\n// after: flat session object with canonical id\nif (/^[0-9a-z]{20}$/.test(session.id)) {\n  await fetchPost('/api/ai/agent/saveSession', session);\n}","handlingStrategy":"validation","validationCode":"const ok = payload && typeof payload === 'object' &&\n  !Array.isArray(payload) &&\n  /^[0-9a-z]{20}$/.test(payload.id ?? '');\nif (!ok) throw new Error('invalid session data');","typeGuard":"const isSessionPayload = (p: unknown): p is {id: string; [k: string]: any} =>\n  !!p && typeof p === 'object' && !Array.isArray(p) &&\n  /^[0-9a-z]{20}$/.test((p as any).id);","tryCatchPattern":"null","preventionTips":["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"],"tags":["agent","session","validation","payload"],"backgroundTag":"request-payload-validation-failed","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}