{"record":{"id":"4e0fd22685f73e02","repo":"CherryHQ/cherry-studio","slug":"path-is-not-a-file-filepath-4e0fd2","errorCode":null,"errorMessage":"Path is not a file: ${filePath}","messagePattern":"Path is not a file: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/servers/filesystem/tools/read.ts","lineNumber":44,"sourceCode":"- Empty files return a warning`,\n  inputSchema: z.toJSONSchema(ReadToolSchema)\n}\n\n// Handler implementation\nexport async function handleReadTool(args: unknown, baseDir: string) {\n  const parsed = ReadToolSchema.safeParse(args)\n  if (!parsed.success) {\n    throw new Error(`Invalid arguments for read: ${parsed.error}`)\n  }\n\n  const filePath = parsed.data.file_path\n  const validPath = await validatePath(filePath, baseDir)\n\n  // Check if file exists\n  try {\n    const stats = await fs.stat(validPath)\n    if (!stats.isFile()) {\n      throw new Error(`Path is not a file: ${filePath}`)\n    }\n  } catch (error: any) {\n    if (error.code === 'ENOENT') {\n      throw new Error(`File not found: ${filePath}`)\n    }\n    throw error\n  }\n\n  // Check if file is binary\n  if (await isBinaryFile(validPath)) {\n    throw new Error(`Cannot read binary file: ${filePath}`)\n  }\n\n  // Read file content\n  const content = await fs.readFile(validPath, 'utf-8')\n  const lines = content.split('\\n')\n\n  // Apply offset and limit","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/servers/filesystem/tools/read.ts#L26-L62","documentation":"Thrown by the read tool after fs.stat succeeds but stats.isFile() is false. The path resolved, passed workspace-root validation, and exists on disk, but it is a directory, FIFO, socket, device, or symlink-to-directory rather than a regular file. The read handler can only stream text content from regular files, so any non-regular inode is rejected here before the binary check or readFile is attempted.","triggerScenarios":"Calling handleReadTool with file_path that resolves to a directory (e.g. '/proj/src' or '/proj/src/'), a named pipe/special device file inside the workspace, or a symlink whose realpath target is a directory. The ENOENT branch is skipped because stat succeeded; only isFile() is false.","commonSituations":"Path built by joining a folder with a missing filename; caller appends a trailing slash; user selects a folder node in a file tree UI and triggers read; a symlink inside the workspace points at a directory elsewhere.","solutions":["Pass the full path to a regular file, removing any trailing slash or directory-only segment.","If the path is a symlink, resolve its target with fs.realpath and confirm it is a regular file before calling read.","When the path source is untrusted (user input, UI selection), pre-check with fs.stat + stats.isFile() before invoking the tool."],"exampleFix":"// before\nawait handleReadTool({ file_path: '/proj/src' }, baseDir) // throws: Path is not a file\n\n// after\nawait handleReadTool({ file_path: '/proj/src/index.ts' }, baseDir)","handlingStrategy":"validation","validationCode":"import fs from 'fs/promises'\nasync function assertReadableFile(absPath: string): Promise<void> {\n  const stats = await fs.stat(absPath) // throws ENOENT if missing\n  if (!stats.isFile()) {\n    throw new Error(`Refusing to read non-file path: ${absPath}`)\n  }\n}\n// call before handleReadTool","typeGuard":"function isRegularFile(stats: fs.Stats): boolean {\n  return stats.isFile()\n}","tryCatchPattern":"try {\n  await handleReadTool(args, baseDir)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Path is not a file')) {\n    // surface 'expected a regular file' to the caller\n  } else throw e\n}","preventionTips":["Build read paths from a verified file listing (fs.readdir) rather than user free text.","Strip trailing slashes and reject directory-shaped paths before calling read.","Resolve symlinks with fs.realpath and re-check isFile() on the target."],"tags":["filesystem","validation","mcp","read-tool"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}