CherryHQ/cherry-studio · error · Error

Not a regular file: ${displayPath}

Error message

Not a regular file: ${displayPath}

What it means

Thrown by readCanonicalLocalFile() when lstat reports the path is not a regular file. The custom lstat from @main/utils/file returns isFile as a boolean (not a method), so the check !stats.isFile fires for directories, symbolic links, device files, sockets, FIFOs, and any other non-regular file type. This prevents attempting to read a directory or special file as a data buffer.

Source

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

 */
export async function readCanonicalLocalFile(
  requestedPath: string,
  canonicalPath: string,
  displayPath: string
): Promise<FileAttachment> {
  const target = AbsoluteFilePathSchema.parse(canonicalPath)

  let stats: Awaited<ReturnType<typeof lstat>>
  try {
    stats = await lstat(target)
  } catch (error) {
    if (isErrnoException(error) && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) {
      throw new Error(`File not found: ${displayPath}`)
    }
    throw error
  }
  if (!stats.isFile) {
    throw new Error(`Not a regular file: ${displayPath}`)
  }

  // The snapshot pins the inode and fixes the read length at open time, so the size
  // check and the read see the same file even if the path is replaced meanwhile.
  const snapshot = await openReadableFileSnapshot(target)
  try {
    if (snapshot.size > MAX_FILE_SIZE_BYTES) {
      throw new Error(`File exceeds the ${MAX_FILE_SIZE_BYTES} byte limit (${snapshot.size} bytes): ${displayPath}`)
    }

    const data = await readStreamToBuffer(snapshot.createReadStream())
    // Re-check against the actual read size: the file can grow between stat and read.
    if (data.length > MAX_FILE_SIZE_BYTES) {
      throw new Error(`File exceeds the ${MAX_FILE_SIZE_BYTES} byte limit (${data.length} bytes): ${displayPath}`)
    }
    const filename = path.basename(requestedPath)
    return {
      filename,

View on GitHub (pinned to 726446b54c)

Solutions

  1. Check that the path points to a regular file, not a directory — re-list the parent directory to see what files vs. directories exist.
  2. If the intent is to read directory contents, use the directory-listing API (listDirectory via @main/services/file/tree) instead of the file-read path.
  3. Filter workspace listings to exclude directories before presenting file choices to the agent.
Defensive patterns

Strategy: validation

Validate before calling

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

// Pre-check that the path is a regular file before resolving
const s = await stat(AbsoluteFilePathSchema.parse(canonicalPath))
if (!s.isFile) {
  throw new Error(`Path is not a regular file — cannot attach: ${displayPath}`)
}

Try / catch

try {
  return await readCanonicalLocalFile(requestedPath, canonicalPath, displayPath)
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Not a regular file:')) {
    logger.warn('Attempted to attach a non-file path', { displayPath })
    return null
  }
  throw error
}

Prevention

When it happens

Trigger: Called after a successful lstat in readCanonicalLocalFile(). Triggers when the resolved path points to a directory, a broken symlink (though that would typically fail lstat with ENOENT), a named pipe, a device node, or a socket. The agent or user supplied a path that is valid but not a readable data file.

Common situations: An agent tried to attach a directory path as a file; the path points to a symlink whose target is a directory; a special file like /dev/null or a FIFO was specified; the agent confused a directory name with a file name from a workspace listing.

Related errors


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