CherryHQ/cherry-studio · critical · Error
Agent storage directory must be a real directory: ${targetPa
Error message
Agent storage directory must be a real directory: ${targetPath} What it means
Thrown by ensureAgentStorageDirectory as its final post-creation check: after ensuring the directory exists and asserting the path is safe, it lstat's the target and requires it to be a real directory (isDirectory true, isSymbolicLink false). This catches the race where, between ensureDir and the check, something replaced the directory with a symlink or a non-directory (TOCTOU hardening).
Source
Thrown at src/main/ai/agents/agentDataDirectory.ts:88
const [realRoot, realTarget] = await Promise.all([
resolveRealOrNearestExistingPath(root),
resolveRealOrNearestExistingPath(target)
])
if (realTarget !== realRoot && !isPathInside(realTarget, realRoot)) {
throw new Error(`Agent storage path resolves outside its root: ${target}`)
}
}
/** Ensure a Data/Agents path is a real directory contained by the Agent storage root. */
export async function ensureAgentStorageDirectory(agentsDataRoot: string, targetPath: string): Promise<void> {
await ensureDir(asAbsolutePath(path.resolve(agentsDataRoot)))
await assertAgentStoragePath(agentsDataRoot, targetPath)
await ensureDir(asAbsolutePath(path.resolve(targetPath)))
await assertAgentStoragePath(agentsDataRoot, targetPath)
const targetStat = await lstat(asAbsolutePath(path.resolve(targetPath)))
if (!targetStat.isDirectory || targetStat.isSymbolicLink) {
throw new Error(`Agent storage directory must be a real directory: ${targetPath}`)
}
}
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) {View on GitHub (pinned to 726446b54c)
Solutions
- Ensure only one app instance/process manages the Data/Agents folder at a time; disable sync/backup tools that rewrite it.
- If a symlink exists at the target, remove it so ensureDir creates a real directory.
- Run on a local filesystem rather than a network/synced drive that may replace directories.
- Retry ensureAgentStorageDirectory once the interfering process is stopped; if it persists, inspect the path with ls -la.
Defensive patterns
Strategy: validation
Validate before calling
import { lstat } from '@main/utils/file'
const st = await lstat(path.resolve(targetPath))
if (!st.isDirectory || st.isSymbolicLink) {
throw new Error(`Refusing: '${targetPath}' is not a real directory`)
} Prevention
- Ensure only one process manages the Data/Agents folder; pause sync/backup tools that rewrite it.
- Remove any symlink at the target path before ensuring the directory.
- Use a local filesystem rather than network/synced drives that may replace directories.
When it happens
Trigger: Immediately after ensureDir creates the directory, another process replaces it with a symlink or a file; or ensureDir resolved through a symlink leaving a link at the target path. The lstat then sees a symlink/non-directory and throws.
Common situations: Concurrent process (sync, backup, antivirus, another app instance) replacing the just-created directory; a symlinked target that ensureDir followed; filesystem oddities on network/SMB drives; a second app instance racing on the same data folder.
Related errors
- Agent storage root must be a real directory: ${root}
- Agent storage path contains a symbolic link: ${current}
- Agent storage path resolves outside its root: ${target}
- Agent data file must be a real file: ${filePath}
- Agent storage path escapes its root: ${target}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/8a23d22e8184d7f2.
Report an issue: GitHub.