{"record":{"id":"049d8162dca7e400","repo":"CherryHQ/cherry-studio","slug":"agent-data-file-must-be-a-real-file-filepath","errorCode":null,"errorMessage":"Agent data file must be a real file: ${filePath}","messagePattern":"Agent data file must be a real file: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/agents/agentDataDirectory.ts","lineNumber":107,"sourceCode":"  }\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) {\n      throw new Error(`Agent data file must be a real file: ${filePath}`)\n    }\n    return\n  }\n  try {\n    const handle = await open(filePath, 'wx', 0o600)\n    await handle.close()\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error\n\n    const racedFile = await lstatIfExists(filePath)\n    if (!racedFile?.isFile || racedFile.isSymbolicLink) {\n      throw new Error(`Agent data file must be a real file: ${filePath}`)\n    }\n  }\n}\n\nexport async function ensureAgentDataDirectory(\n  agentsDataRoot: string,","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/agents/agentDataDirectory.ts#L89-L125","documentation":"Thrown by ensureEmptyFile when preparing a per-agent data file whose target path already exists but is not a plain regular file. The check uses lstat (no symlink follow) and explicitly rejects symbolic links, so even a symlink pointing at a valid file is treated as unsafe. The guard exists because the agents data store must only ever hold real files written by the app, blocking symlink-swap and TOCTOU attacks on agent data.","triggerScenarios":"ensureAgentDataDirectory(agentsDataRoot, agentId) (which calls ensureEmptyFile for each AGENT_DATA_FILES entry) runs against a path under {agentsDataRoot}/{agentId}/ that already holds a directory, symlink, FIFO, socket, or device instead of an empty regular file.","commonSituations":"The agents data root was restored from a backup that preserved symlinks; a cloud-sync folder (Dropbox/iCloud/OneDrive) replaced a data file with a placeholder symlink; a prior crash left a directory where a file was expected; manual tampering or a third-party tool wrote into the data dir.","solutions":["Inspect the offending path: run `ls -la <filePath>` and `readlink <filePath>` to identify what entry occupies it.","If it is a stray symlink/directory and not real agent data, remove it so the app can recreate a real 0600 file.","Move the agents data root out of any cloud-synced or network-mounted folder.","Confirm the app process owns the directory and no other process writes symlinks into it."],"exampleFix":"// before: data file replaced by a sync placeholder\nappData/agents/<id>/agent.json -> /cloud/agent.json\n\n// after: remove the symlink so ensureEmptyFile can create a real file\nrm appData/agents/<id>/agent.json\n// next ensureAgentDataDirectory() recreates it with mode 0600","handlingStrategy":"validation","validationCode":"import { lstat } from 'node:fs/promises'\nasync function isSafeDataFileTarget(p: string): Promise<boolean> {\n  try {\n    const s = await lstat(p)\n    return s.isFile() && !s.isSymbolicLink()\n  } catch {\n    return true // absent path is safe to create\n  }\n}\nif (!(await isSafeDataFileTarget(filePath))) {\n  throw new Error(`Refusing to provision: unsafe entry at ${filePath}`)\n}\nawait ensureAgentDataDirectory(agentsDataRoot, agentId)","typeGuard":"function isRegularFile(stat: import('node:fs').Stats): boolean {\n  return stat.isFile() && !stat.isSymbolicLink()\n}","tryCatchPattern":"try {\n  await ensureAgentDataDirectory(root, agentId)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Agent data file must be a real file')) {\n    // surface a 'corrupt agent data store' error to the user; do NOT auto-delete\n  } else throw e\n}","preventionTips":["Keep the agents data root outside cloud-synced/network-mounted folders.","Never create symlinks inside the agents data directory.","Run the app against a dedicated data directory it fully owns."],"tags":["filesystem","security","symlink","agents","toctou"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}