CherryHQ/cherry-studio · critical · Error

Agent storage root must be a real directory: ${root}

Error message

Agent storage root must be a real directory: ${root}

What it means

Thrown by assertAgentStoragePath when the agents data root itself is missing, not a directory, or is a symbolic link. The check uses lstat (does not follow symlinks) and requires rootStat.isDirectory truthy and isSymbolicLink false. This ensures the managed root is a real directory before any segment walking begins, preventing a symlinked root from redirecting all operations.

Source

Thrown at src/main/ai/agents/agentDataDirectory.ts:54

      }
    }
  }
}

/**
 * Validate a path in Data/Agents without following symbolic links in the
 * managed root or any path component below it.
 */
export async function assertAgentStoragePath(agentsDataRoot: string, targetPath: string): Promise<void> {
  const root = asAbsolutePath(path.resolve(agentsDataRoot))
  const target = asAbsolutePath(path.resolve(targetPath))
  if (target !== root && !isPathInside(target, root)) {
    throw new Error(`Agent storage path escapes its root: ${target}`)
  }

  const rootStat = await lstatIfExists(root)
  if (!rootStat?.isDirectory || rootStat.isSymbolicLink) {
    throw new Error(`Agent storage root must be a real directory: ${root}`)
  }

  let current = root
  const relative = path.relative(root, target)
  for (const segment of relative ? relative.split(path.sep) : []) {
    current = asAbsolutePath(path.join(current, segment))
    const currentStat = await lstatIfExists(current)
    if (!currentStat) break
    if (currentStat.isSymbolicLink) {
      throw new Error(`Agent storage path contains a symbolic link: ${current}`)
    }
    if (current !== target && !currentStat.isDirectory) {
      throw new Error(`Agent storage path parent is not a directory: ${current}`)
    }
  }

  const [realRoot, realTarget] = await Promise.all([
    resolveRealOrNearestExistingPath(root),

View on GitHub (pinned to 726446b54c)

Solutions

  1. Create the root as a real directory first via ensureAgentStorageDirectory (which calls ensureDir then asserts).
  2. If the root is a symlink or file, remove/replace it with a real directory — do not weaken the check.
  3. Ensure no external process (sync, backup, migration tool) replaces the Data/Agents directory with a symlink.
  4. Verify the application path configuration resolves agentsDataRoot to the intended on-disk directory.

Example fix

// before (asserting before the root exists)
await assertAgentStoragePath(agentsDataRoot, target) // root missing -> throws

// after
await ensureAgentStorageDirectory(agentsDataRoot, target) // creates + asserts
Defensive patterns

Strategy: validation

Validate before calling

import { lstatIfExists } from './agentDataDirectory' // or inline lstat
const stat = await lstatIfExists(path.resolve(agentsDataRoot))
if (!stat?.isDirectory || stat.isSymbolicLink) {
  throw new Error(`Agents root '${agentsDataRoot}' must be a real directory`)
}

Prevention

When it happens

Trigger: The Data/Agents root does not exist yet (not created); it exists but is a file rather than a directory; it is a symbolic link to another location. Calling assertAgentStoragePath before ensureAgentStorageDirectory has created the root would also surface this.

Common situations: First run before the directory is initialized; a user or another process replaced the directory with a symlink or a file; a corrupted/Partially-set-up data folder; calling the assert directly instead of via ensureAgentStorageDirectory.

Related errors


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