agalwood/Motrix · error · FsStorageError

plugin.fs.not_a_file

plugin.fs.not_a_file

Error message

plugin.fs.not_a_file: ${relPath} is a directory

What it means

Thrown by FsStorage.read() when `fs.readFile` fails with EISDIR — the relPath resolves to a directory, not a regular file, so reading it as a file is invalid. Code is `plugin.fs.not_a_file`. Distinct from not_found (ENOENT) which is checked first in the same catch block.

Source

Thrown at src/core/plugin/capabilities/fs-storage.ts:128

  ): Promise<string | Uint8Array> {
    const encoding = opts?.encoding ?? 'utf8'
    const abs = await resolveInsideSandbox(this.root, relPath)
    try {
      if (encoding === 'utf8') {
        return await fs.readFile(abs, 'utf8')
      }
      const buf = await fs.readFile(abs)
      return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
    } catch (e: unknown) {
      const err = e as NodeJS.ErrnoException
      if (err.code === 'ENOENT') {
        throw new FsStorageError(
          'plugin.fs.not_found',
          `plugin.fs.not_found: ${relPath}`
        )
      }
      if (err.code === 'EISDIR') {
        throw new FsStorageError(
          'plugin.fs.not_a_file',
          `plugin.fs.not_a_file: ${relPath} is a directory`
        )
      }
      throw e
    }
  }

  // -------------------------------------------------------------------------
  // write (atomic)
  // -------------------------------------------------------------------------

  async write(
    relPath: string,
    data: string | Uint8Array,
    opts?: { overwrite?: boolean; encoding?: 'utf8' | 'binary' }
  ): Promise<void> {
    const overwrite = opts?.overwrite ?? true

View on GitHub (pinned to 1a708ee577)

Solutions

  1. stat() the path first and branch on `isFile`/`isDirectory` before reading.
  2. Filter directory entries out when iterating list() output before read().
  3. Construct read paths with explicit filename suffixes to avoid landing on a directory.
  4. If the caller expects either, dispatch on stat result rather than blindly reading.

Example fix

// before
const data = await storage.read(entry) // entry may be a dir from list()

// after
const s = await storage.stat(entry)
if (!s.isFile) throw new Error(`${entry} is not a file`)
const data = await storage.read(entry)
Defensive patterns

Strategy: validation

Validate before calling

async function assertIsFile(storage: FsStorage, rel: string): Promise<void> {
  const s = await storage.stat(rel)
  if (!s.isFile) throw new Error(`${rel} is not a regular file`)
}

Type guard

function isNotAFile(e: unknown): boolean {
  return e instanceof Error && (e as FsStorageError).code === 'plugin.fs.not_a_file'
}

Try / catch

try {
  const data = await storage.read(rel)
} catch (e) {
  if (isNotAFile(e)) { /* skip or route to a directory handler */ }
  else throw e
}

Prevention

When it happens

Trigger: Calling `storage.read('somedir')` where somedir is a directory; passing a path that omits the filename and lands on the directory entry; using a directory path from listing output as a read target.

Common situations: Off-by-one in path construction drops the filename; plugin iterates entries from list() and forgets to skip directories; manifest/output path collides with an existing directory name.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/09b0760ef1aec4cc. Report an issue: GitHub.