CherryHQ/cherry-studio · critical · Error
Invalid agent id for data directory: ${agentId}
Error message
Invalid agent id for data directory: ${agentId} What it means
Thrown by assertAgentId when the agentId is empty, the special traversal names '.' or '..', the reserved name 'system' (case-insensitive), or contains any path separator (/ or \). agentId is used to construct the agent data directory via path.join(agentsDataRoot, agentId), so these values would either traverse the filesystem or collide with the reserved system namespace. The guard runs in agentDataDirectoryPath before any path is built.
Source
Thrown at src/main/ai/agents/agentDataDirectory.ts:94
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) {
throw new Error(`Agent data file must be a real file: ${filePath}`)
}
return
}
try {
const handle = await open(filePath, 'wx', 0o600)View on GitHub (pinned to 726446b54c)
Solutions
- Generate agent ids from a safe alphabet (UUIDs, base64url, alnum) that cannot contain separators or be empty.
- Validate ids at the point of creation (agent creation form/service) against the same rules before persisting.
- If migrating data, sanitize or reject ids containing path separators before they reach the filesystem layer.
- Reserve 'system' at the id-generation layer so users/imports cannot mint it.
Example fix
// before await ensureAgentDataDirectory(root, userInput) // userInput may be 'a/b' or '' // after const id = crypto.randomUUID() // safe, separator-free await ensureAgentDataDirectory(root, id)
Defensive patterns
Strategy: validation
Validate before calling
import path from 'node:path'
function assertAgentId(agentId: string): void {
if (!agentId || agentId === '.' || agentId === '..' || agentId.toLowerCase() === 'system' || /[\\/]/.test(agentId)) {
throw new Error(`Invalid agent id: ${agentId}`)
}
}
assertAgentId(candidate) Type guard
const isValidAgentId = (id: string): boolean => !!id && id !== '.' && id !== '..' && id.toLowerCase() !== 'system' && !/[\\/]/.test(id)
Prevention
- Generate agent ids from a safe alphabet (UUID / base64url) — never raw user input.
- Validate ids at agent-creation time against the same rules before persisting.
- Sanitize imported/migrated ids that contain separators before they reach the filesystem layer.
- Reserve 'system' at the id-generation layer so it can never be minted.
When it happens
Trigger: Calling agentDataDirectoryPath (directly or via ensureAgentDataDirectory/createAgentDataDirectory) with an empty agentId; an agentId of 'system'; an id containing '/' or '\' (e.g. 'foo/bar' or 'a\b'); or the literal '.' / '..' strings.
Common situations: A caller passes an unsanitized external id; an import/migration produced an id with a path separator; a generated id collided with 'system'; an empty string from a form field or failed id generation; cross-platform id with a Windows backslash.
Related errors
- Agent storage path escapes its root: ${target}
- Agent storage root must be a real directory: ${root}
- Agent storage path contains a symbolic link: ${current}
- Agent storage path parent is not a directory: ${current}
- Agent storage path resolves outside its root: ${target}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/4b4adf957d089d0e.
Report an issue: GitHub.