CherryHQ/cherry-studio · error · Error
Agent data file must be a real file: ${filePath}
Error message
Agent data file must be a real file: ${filePath} What it means
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.
Source
Thrown at src/main/ai/agents/agentDataDirectory.ts:107
}
}
function assertAgentId(agentId: string): void {
if (!agentId || agentId === '.' || agentId === '..' || agentId.toLowerCase() === 'system' || /[\\/]/.test(agentId)) {
throw new Error(`Invalid agent id for data directory: ${agentId}`)
}
}
export function agentDataDirectoryPath(agentsDataRoot: string, agentId: string): string {
assertAgentId(agentId)
return path.join(agentsDataRoot, agentId)
}
async function ensureEmptyFile(filePath: string): Promise<void> {
const existing = await lstatIfExists(filePath)
if (existing) {
if (!existing.isFile || existing.isSymbolicLink) {
throw new Error(`Agent data file must be a real file: ${filePath}`)
}
return
}
try {
const handle = await open(filePath, 'wx', 0o600)
await handle.close()
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
const racedFile = await lstatIfExists(filePath)
if (!racedFile?.isFile || racedFile.isSymbolicLink) {
throw new Error(`Agent data file must be a real file: ${filePath}`)
}
}
}
export async function ensureAgentDataDirectory(
agentsDataRoot: string,View on GitHub (pinned to 726446b54c)
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.
Example fix
// before: data file replaced by a sync placeholder appData/agents/<id>/agent.json -> /cloud/agent.json // after: remove the symlink so ensureEmptyFile can create a real file rm appData/agents/<id>/agent.json // next ensureAgentDataDirectory() recreates it with mode 0600
Defensive patterns
Strategy: validation
Validate before calling
import { lstat } from 'node:fs/promises'
async function isSafeDataFileTarget(p: string): Promise<boolean> {
try {
const s = await lstat(p)
return s.isFile() && !s.isSymbolicLink()
} catch {
return true // absent path is safe to create
}
}
if (!(await isSafeDataFileTarget(filePath))) {
throw new Error(`Refusing to provision: unsafe entry at ${filePath}`)
}
await ensureAgentDataDirectory(agentsDataRoot, agentId) Type guard
function isRegularFile(stat: import('node:fs').Stats): boolean {
return stat.isFile() && !stat.isSymbolicLink()
} Try / catch
try {
await ensureAgentDataDirectory(root, agentId)
} catch (e) {
if (e instanceof Error && e.message.startsWith('Agent data file must be a real file')) {
// surface a 'corrupt agent data store' error to the user; do NOT auto-delete
} else throw e
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Agent storage directory must be a real directory: ${targetPa
- Agent data directory must be a real directory: ${agentDataPa
- Agent memory directory must be a real directory: ${memoryPat
- Refusing to recursively remove unsafe agent data path: ${age
- Required agent prompt file is not a regular file: ${exact}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/049d8162dca7e400.
Report an issue: GitHub.