{"record":{"id":"732f9b40ddc7e777","repo":"siyuan-note/siyuan","slug":"invalid-agent-session-id","errorCode":null,"errorMessage":"invalid Agent session ID","messagePattern":"invalid Agent session ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/todo.go","lineNumber":46,"sourceCode":")\n\ntype AgentTodoItem struct {\n\tContent string `json:\"content\"`\n\tStatus  string `json:\"status\"` // pending, in_progress, completed, cancelled\n}\n\ntype AgentTodoList struct {\n\tSessionID string          `json:\"sessionID\"`\n\tTodos     []AgentTodoItem `json:\"todos\"`\n}\n\nfunc agentTodosPath(sessionID string) string {\n\treturn filepath.Join(util.DataDir, \"storage\", \"ai\", \"agent\", \"sessions\", sessionID, \"todos.json\")\n}\n\nfunc SaveAgentTodos(sessionID string, todos []AgentTodoItem) error {\n\tif !ast.IsNodeIDPattern(sessionID) {\n\t\treturn errors.New(\"invalid Agent session ID\")\n\t}\n\tdir := filepath.Join(util.DataDir, \"storage\", \"ai\", \"agent\", \"sessions\", sessionID)\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn err\n\t}\n\n\tdata := AgentTodoList{\n\t\tSessionID: sessionID,\n\t\tTodos:     todos,\n\t}\n\n\tb, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn filelock.WriteFile(agentTodosPath(sessionID), b)\n}","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/todo.go#L28-L64","documentation":"SaveAgentTodos persists an AI-agent todo list under data/storage/ai/agent/sessions/<sessionID>/todos.json. The sessionID is interpolated into a filesystem path, so the kernel validates it against ast.IsNodeIDPattern (the SiYuan node-ID format) before touching disk. A session ID not matching that pattern is rejected to prevent path traversal or writing into unexpected directories.","triggerScenarios":"Calling SaveAgentTodos (or the todoWriteHandler HTTP endpoint) with a sessionID that is not a valid node ID (empty string, contains '/' or '..' traversal, arbitrary UUID, or other malformed identifier).","commonSituations":"Client generates its own session IDs (UUIDs) instead of using SiYuan node IDs; an agent framework passes a conversation key with slashes; sessionID empty because a previous lookup failed and returned ''.","solutions":["Generate session IDs in the SiYuan node-ID format (YYYYMMDDhhmmss-abcdefg style accepted by ast.IsNodeIDPattern)","Sanitize the caller's session ID to conform to the node-ID pattern before calling the API","If the session came from a prior API response, pass that ID through unchanged instead of reformatting it"],"exampleFix":"// before: arbitrary session key\nsaveAgentTodos(conv.ID, todos); // e.g. \"org/conv/123\"\n// after: use a valid node ID\nconst sessionId = new Date().toISOString().replace(/[-:TZ.]/g, \"\") + \"-\" + random7Chars();\nsaveAgentTodos(sessionId, todos);","handlingStrategy":"validation","validationCode":"const NODE_ID_RE = /^\\d{14}-[0-9a-z]{7}$/;\nif (!NODE_ID_RE.test(sessionId)) {\n  throw new Error('session ID must be a SiYuan node ID');\n}\nawait fetchPost('/api/ai/agent/todo/write', {sessionID: sessionId, todos});","typeGuard":"const isValidSessionId = (id) =>\n  typeof id === 'string' && /^\\d{14}-[0-9a-z]{7}$/.test(id);","tryCatchPattern":"try {\n  await saveAgentTodos(sessionId, todos);\n} catch (e) {\n  if (String(e.message).includes('invalid Agent session ID')) {\n    sessionId = newNodeId(); // regenerate in valid format and retry\n    await saveAgentTodos(sessionId, todos);\n  } else { throw e; }\n}","preventionTips":["Always derive session IDs from SiYuan node IDs, never client-generated UUIDs","Validate IDs against the node-ID pattern before any kernel call","Never build session paths manually; pass the ID and let the kernel compose the path"],"tags":["validation","agent","path-traversal","session-id"],"backgroundTag":"invalid-identifier-format","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}