{"record":{"id":"8a23d22e8184d7f2","repo":"CherryHQ/cherry-studio","slug":"agent-storage-directory-must-be-a-real-directory","errorCode":null,"errorMessage":"Agent storage directory must be a real directory: ${targetPath}","messagePattern":"Agent storage directory must be a real directory: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/main/ai/agents/agentDataDirectory.ts","lineNumber":88,"sourceCode":"\n  const [realRoot, realTarget] = await Promise.all([\n    resolveRealOrNearestExistingPath(root),\n    resolveRealOrNearestExistingPath(target)\n  ])\n  if (realTarget !== realRoot && !isPathInside(realTarget, realRoot)) {\n    throw new Error(`Agent storage path resolves outside its root: ${target}`)\n  }\n}\n\n/** Ensure a Data/Agents path is a real directory contained by the Agent storage root. */\nexport async function ensureAgentStorageDirectory(agentsDataRoot: string, targetPath: string): Promise<void> {\n  await ensureDir(asAbsolutePath(path.resolve(agentsDataRoot)))\n  await assertAgentStoragePath(agentsDataRoot, targetPath)\n  await ensureDir(asAbsolutePath(path.resolve(targetPath)))\n  await assertAgentStoragePath(agentsDataRoot, targetPath)\n  const targetStat = await lstat(asAbsolutePath(path.resolve(targetPath)))\n  if (!targetStat.isDirectory || targetStat.isSymbolicLink) {\n    throw new Error(`Agent storage directory must be a real directory: ${targetPath}`)\n  }\n}\n\nfunction assertAgentId(agentId: string): void {\n  if (!agentId || agentId === '.' || agentId === '..' || agentId.toLowerCase() === 'system' || /[\\\\/]/.test(agentId)) {\n    throw new Error(`Invalid agent id for data directory: ${agentId}`)\n  }\n}\n\nexport function agentDataDirectoryPath(agentsDataRoot: string, agentId: string): string {\n  assertAgentId(agentId)\n  return path.join(agentsDataRoot, agentId)\n}\n\nasync function ensureEmptyFile(filePath: string): Promise<void> {\n  const existing = await lstatIfExists(filePath)\n  if (existing) {\n    if (!existing.isFile || existing.isSymbolicLink) {","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/agents/agentDataDirectory.ts#L70-L106","documentation":"Thrown by ensureAgentStorageDirectory as its final post-creation check: after ensuring the directory exists and asserting the path is safe, it lstat's the target and requires it to be a real directory (isDirectory true, isSymbolicLink false). This catches the race where, between ensureDir and the check, something replaced the directory with a symlink or a non-directory (TOCTOU hardening).","triggerScenarios":"Immediately after ensureDir creates the directory, another process replaces it with a symlink or a file; or ensureDir resolved through a symlink leaving a link at the target path. The lstat then sees a symlink/non-directory and throws.","commonSituations":"Concurrent process (sync, backup, antivirus, another app instance) replacing the just-created directory; a symlinked target that ensureDir followed; filesystem oddities on network/SMB drives; a second app instance racing on the same data folder.","solutions":["Ensure only one app instance/process manages the Data/Agents folder at a time; disable sync/backup tools that rewrite it.","If a symlink exists at the target, remove it so ensureDir creates a real directory.","Run on a local filesystem rather than a network/synced drive that may replace directories.","Retry ensureAgentStorageDirectory once the interfering process is stopped; if it persists, inspect the path with ls -la."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { lstat } from '@main/utils/file'\nconst st = await lstat(path.resolve(targetPath))\nif (!st.isDirectory || st.isSymbolicLink) {\n  throw new Error(`Refusing: '${targetPath}' is not a real directory`)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure only one process manages the Data/Agents folder; pause sync/backup tools that rewrite it.","Remove any symlink at the target path before ensuring the directory.","Use a local filesystem rather than network/synced drives that may replace directories."],"tags":["security","filesystem","symlink","toctou","agent"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}