CherryHQ/cherry-studio · error · Error
Agent memory file must be a real file: ${filePath}
Error message
Agent memory file must be a real file: ${filePath} What it means
assertRegularFileOrMissing() is called before writing FACT.md or JOURNAL.jsonl. If the target exists and lstat shows it is not a regular file or is a symlink, it throws. This is a TOCTOU defense: it re-validates the file type immediately before the write, complementing the earlier resolveFileCI check.
Source
Thrown at src/main/ai/mcp/servers/agentMemory.ts:199
}
return assertedPath
}
private async assertMemoryDirectory(): Promise<string> {
const agentDataPath = await this.getAgentDataPath()
const memoryDir = path.join(agentDataPath, 'memory')
const memoryStat = await lstat(memoryDir)
if (!memoryStat.isDirectory() || memoryStat.isSymbolicLink()) {
throw new Error(`Agent memory directory must be a real directory: ${memoryDir}`)
}
return memoryDir
}
private async assertRegularFileOrMissing(filePath: string): Promise<void> {
try {
const fileStat = await lstat(filePath)
if (!fileStat.isFile() || fileStat.isSymbolicLink()) {
throw new Error(`Agent memory file must be a real file: ${filePath}`)
}
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
}
}
private async memoryUpdate(args: Record<string, string | undefined>) {
const content = args.content
if (!content) throw new McpError(ErrorCode.InvalidParams, "'content' is required for update action")
const memoryDir = await this.assertMemoryDirectory()
const factPath = await resolveFileCI(memoryDir, 'FACT.md')
await this.assertRegularFileOrMissing(factPath)
// Atomic write via temp file + rename
const tmpPath = path.join(memoryDir, `.FACT.md.${randomUUID()}.tmp`)
const handle = await open(tmpPath, 'wx', 0o600)
try {View on GitHub (pinned to 726446b54c)
Solutions
- Investigate what process replaced the file between resolution and write.
- Remove the offending non-regular entry so the write can recreate a normal file.
- Run the memory tool again after cleanup.
Example fix
# before: FACT.md swapped to symlink mid-flight ls -la memory/FACT.md # symlink # after rm memory/FACT.md # re-run the update action; temp file + rename will recreate a regular file
Defensive patterns
Strategy: validation
Validate before calling
import { lstat } from 'node:fs/promises'
async function isRegularOrMissing(p: string): Promise<boolean> {
try {
const s = await lstat(p)
return s.isFile() && !s.isSymbolicLink()
} catch (err) {
return (err as NodeJS.ErrnoException).code === 'ENOENT'
}
} Prevention
- Treat the pre-write assert as a TOCTOU defense; keep the window between resolve and write small.
- Do not run external processes that swap memory files for symlinks during operation.
- If you observe this in production, suspect an adversarial local process or a buggy sync tool.
When it happens
Trigger: Between resolveFileCI and the write, the target file was replaced with a symlink, directory, or special file. The pre-write check at line 197-200 catches the swap.
Common situations: A race condition where another process swaps FACT.md for a symlink mid-operation; an adversarial local process attempting a TOCTOU attack; a sync tool creating symlinks during operation.
Related errors
- Agent memory file must be a real file: ${exact}
- Agent memory file must be a real file: ${matchedPath}
- Agent memory directory must be a real directory: ${memoryDir
- Agent journal must be a regular file: ${journalPath}
- Agent storage directory must be a real directory: ${targetPa
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/426ec2fceea48bd7.
Report an issue: GitHub.