CherryHQ/cherry-studio · error · Error

File not found

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.

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.

Appendix: 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)