{"record":{"id":"dc8d62dac817be88","repo":"CherryHQ/cherry-studio","slug":"file-not-found-userpath","errorCode":null,"errorMessage":"File not found: ${userPath}","messagePattern":"File not found: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/channels/security/localFileResolver.ts","lineNumber":82,"sourceCode":"  } finally {\n    // Swallow close errors so they can't mask an in-flight resolution error.\n    await snapshot.close().catch(() => {})\n  }\n}\n\n/**\n * Resolve and read a local file. Relative paths are resolved from `basePath`.\n * NOTE: no containment check — an absolute `userPath` escapes `basePath`.\n */\nexport async function resolveLocalFile(basePath: string, userPath: string): Promise<FileAttachment> {\n  const requestedPath = path.resolve(basePath, userPath)\n\n  let canonicalPath: string\n  try {\n    canonicalPath = await realpath(AbsoluteFilePathSchema.parse(requestedPath))\n  } catch (error) {\n    if (isErrnoException(error) && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) {\n      throw new Error(`File not found: ${userPath}`)\n    }\n    throw error\n  }\n\n  return readCanonicalLocalFile(requestedPath, canonicalPath, userPath)\n}\n","sourceCodeStart":64,"sourceCodeEnd":89,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/channels/security/localFileResolver.ts#L64-L89","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the file exists at the resolved absolute path before calling resolveLocalFile.","If the path is workspace-relative, use resolveWorkspaceFile instead — it provides better error messages and containment.","Check for typos, case sensitivity issues, or missing path components."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { exists } from '@main/utils/file'\n\nconst resolvedPath = path.resolve(basePath, userPath)\nif (!(await exists(resolvedPath))) {\n  logger.warn('File does not exist', { userPath, resolvedPath })\n  return null\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await resolveLocalFile(basePath, userPath)\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('File not found:')) {\n    return null\n  }\n  throw error\n}","preventionTips":["Verify file existence before calling resolveLocalFile.","Prefer resolveWorkspaceFile when containment is needed — it provides better error messages.","Remember that resolveLocalFile does NOT check containment — an absolute userPath escapes basePath by design."],"tags":["filesystem","file-not-found","enoent","channels"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}