{"record":{"id":"33fdc86eb94e6ed2","repo":"siyuan-note/siyuan","slug":"invalid-session-id-33fdc8","errorCode":null,"errorMessage":"invalid session id","messagePattern":"invalid session id","errorType":"http","errorClass":null,"httpStatus":200,"severity":"error","filePath":"kernel/agent/session.go","lineNumber":286,"sourceCode":"\t\t}\n\t}\n\tend := min(start+pageSize, total)\n\n\treturn &SessionListResult{\n\t\tSessions: items[start:end],\n\t\tTotal:    total,\n\t\tPage:     page,\n\t\tPageSize: pageSize,\n\t}\n}\n\nfunc GetSession(id string) (map[string]any, error) {\n\treturn GetSessionState(id, true)\n}\n\nfunc GetSessionState(id string, includeRuntime bool) (map[string]any, error) {\n\tif id == \"\" || !isValidSessionID(id) {\n\t\treturn nil, fmt.Errorf(\"invalid session id\")\n\t}\n\tlock := sessionLock(id)\n\tlock.Lock()\n\tdefer lock.Unlock()\n\n\tsessionPath := filepath.Join(sessionsDir(), id, \"session.json\")\n\tdata, err := os.ReadFile(sessionPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar session map[string]any\n\tif err := gulu.JSON.UnmarshalJSON(data, &session); err != nil {\n\t\treturn nil, err\n\t}\n\tif includeRuntime {\n\t\tif err := mergeRuntimeIntoSessionLocked(id, session); err != nil {\n\t\t\treturn nil, err\n\t\t}","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/agent/session.go#L268-L304","documentation":"GetSessionState (kernel/agent/session.go:285) validates the requested session id against SiYuan's block-ID pattern (ast.IsNodeIDPattern — exactly 20 chars of [0-9a-z]) before touching disk. An empty string or anything that is not a canonical 20-char id yields 'invalid session id'. Sessions are created by the agent itself, so ids always come from lsSessions/getSession responses, never from user input.","triggerScenarios":"POST /api/ai/agent/getSession with {\"id\": \"\"}, a truncated id, an id containing uppercase/whitespace/hyphens, or a value that is actually the session title or index position. Also triggered by internal callers passing an unvalidated external string to agent.GetSession/GetSessionState.","commonSituations":"Frontend typos or hand-constructed test ids; passing an object {id: {…}} so JSON binding yields \"\"; ids mangled by URL encoding or copy-paste; using a database row id from another system.","solutions":["Always take the id from the lsSessions response's sessions[].id field","If generating a test session, use a real 20-char [0-9a-z] id and create the session via saveSession first","Trim whitespace and lowercase the value before sending","Return a 400-style message to the UI instead of retrying — this error is not transient"],"exampleFix":"// before\nfetchPost('/api/ai/agent/getSession', {id: sessionTitle});\n\n// after\nconst list = await fetchPost('/api/ai/agent/lsSessions', {page: 1, pageSize: 30});\nconst item = list.data.sessions.find(s => s.title === sessionTitle);\nif (item) fetchPost('/api/ai/agent/getSession', {id: item.id});","handlingStrategy":"validation","validationCode":"const SESSION_ID_RE = /^[0-9a-z]{20}$/;\nconst ok = typeof id === 'string' && SESSION_ID_RE.test(id);\nif (!ok) throw new Error('invalid session id');","typeGuard":"const isValidSessionID = (id: unknown): id is string =>\n  typeof id === 'string' && /^[0-9a-z]{20}$/.test(id);","tryCatchPattern":"null","preventionTips":["Source ids only from lsSessions/getSession responses","Never accept free-text ids from the UI for session operations","Assert the 20-char [0-9a-z] pattern at the boundary where ids enter your code"],"tags":["agent","session","validation","id-format"],"backgroundTag":"invalid-identifier-format","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}