CherryHQ/cherry-studio · error · Error
Agent memory directory must be a real directory: ${memoryDir
Error message
Agent memory directory must be a real directory: ${memoryDir} What it means
assertMemoryDirectory() lstats <agentDataPath>/memory and requires it to be a real directory (isDirectory() true AND not a symlink). Symlinks are explicitly rejected to prevent a symlink swap replacing the directory with a link elsewhere. This guards every memory read and write.
Source
Thrown at src/main/ai/mcp/servers/agentMemory.ts:190
}
private async getAgentDataPath(): Promise<string> {
// Deliberate existence check: memory writes must stop once the owning agent is gone.
const agent = agentService.getAgent(this.agentId)
if (!agent) throw new McpError(ErrorCode.InternalError, `Agent not found: ${this.agentId}`)
const assertedPath = await assertAgentDataDirectory(path.dirname(this.agentDataPath), this.agentId)
if (path.resolve(assertedPath) !== path.resolve(this.agentDataPath)) {
throw new McpError(ErrorCode.InternalError, `Agent data path mismatch for ${this.agentId}`)
}
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")View on GitHub (pinned to 726446b54c)
Solutions
- Inspect <agentDataPath>/memory and remove the non-directory entry.
- Recreate it as a real directory: rm <path>/memory && mkdir <path>/memory.
- If the agent data directory is corrupt, recreate the agent.
Example fix
# before ls -la agents/X/ # memory -> /tmp/evil (symlink) # after rm agents/X/memory mkdir agents/X/memory
Defensive patterns
Strategy: validation
Validate before calling
import { lstat } from 'node:fs/promises'
async function isSafeDirectory(p: string): Promise<boolean> {
try {
const s = await lstat(p)
return s.isDirectory() && !s.isSymbolicLink()
} catch {
return false
}
} Type guard
function isRealDirectory(stat: import('node:fs').Stats): boolean {
return stat.isDirectory() && !stat.isSymbolicLink()
} Prevention
- Always create the memory directory with fs.mkdir, never via symlink.
- Do not let sync tools convert directories under userData into symlinks.
- Run periodic integrity checks on agent data directories in CI.
When it happens
Trigger: The memory subdirectory exists but is a symlink, a regular file, or another non-directory type. Fires on the first call to memoryUpdate, memoryAppend, or memorySearch.
Common situations: An attacker or sync tool replaced memory/ with a symlink; a file named 'memory' was created instead of a directory; filesystem corruption; a partial backup restore left memory as a file.
Related errors
- Agent memory file must be a real file: ${exact}
- Agent memory file must be a real file: ${matchedPath}
- Agent memory file must be a real file: ${filePath}
- Agent journal must be a regular file: ${journalPath}
- Agent storage path escapes its root: ${target}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/0f0536ac496965f0.
Report an issue: GitHub.