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

  1. Inspect the offending path: run `ls -la <filePath>` and `readlink <filePath>` to identify what entry occupies it.
  2. If it is a stray symlink/directory and not real agent data, remove it so the app can recreate a real 0600 file.
  3. Move the agents data root out of any cloud-synced or network-mounted folder.
  4. 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

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


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/049d8162dca7e400. Report an issue: GitHub.