CherryHQ/cherry-studio · error · Error

File not found: ${userPath}

Error message

File not found: ${userPath}

What it means

Thrown by resolveLocalFile() when realpath(requestedPath) fails with ENOENT or ENOTDIR. resolveLocalFile is the unconstrained file resolver: it resolves relative paths from basePath and follows symlinks via realpath, but performs NO containment check — an absolute userPath escapes basePath by design (see the NOTE in the source comment). This error means the file simply does not exist at the resolved path.

Source

Thrown at src/main/ai/channels/security/localFileResolver.ts:82

  } finally {
    // Swallow close errors so they can't mask an in-flight resolution error.
    await snapshot.close().catch(() => {})
  }
}

/**
 * Resolve and read a local file. Relative paths are resolved from `basePath`.
 * NOTE: no containment check — an absolute `userPath` escapes `basePath`.
 */
export async function resolveLocalFile(basePath: string, userPath: string): Promise<FileAttachment> {
  const requestedPath = path.resolve(basePath, userPath)

  let canonicalPath: string
  try {
    canonicalPath = await realpath(AbsoluteFilePathSchema.parse(requestedPath))
  } catch (error) {
    if (isErrnoException(error) && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) {
      throw new Error(`File not found: ${userPath}`)
    }
    throw error
  }

  return readCanonicalLocalFile(requestedPath, canonicalPath, userPath)
}

View on GitHub (pinned to 726446b54c)

Solutions

  1. Verify the file exists at the resolved absolute path before calling resolveLocalFile.
  2. If the path is workspace-relative, use resolveWorkspaceFile instead — it provides better error messages and containment.
  3. Check for typos, case sensitivity issues, or missing path components.
Defensive patterns

Strategy: validation

Validate before calling

import { exists } from '@main/utils/file'

const resolvedPath = path.resolve(basePath, userPath)
if (!(await exists(resolvedPath))) {
  logger.warn('File does not exist', { userPath, resolvedPath })
  return null
}

Try / catch

try {
  return await resolveLocalFile(basePath, userPath)
} catch (error) {
  if (error instanceof Error && error.message.startsWith('File not found:')) {
    return null
  }
  throw error
}

Prevention

When it happens

Trigger: Called from cherryDocumentTools.ts:90 when the tool decides the path is not a workspace-relative path and falls back to the unconstrained resolver. Fails when the file does not exist, a path component is not a directory (ENOTDIR), or a symlink is dangling.

Common situations: An agent supplied an absolute path to a file that does not exist on the host; a relative path that, when resolved from basePath, points to a non-existent location; the file was deleted between a prior existence check and the realpath call.

Related errors


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