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 ?? trueView on GitHub (pinned to 1a708ee577)
Solutions
- stat() the path first and branch on `isFile`/`isDirectory` before reading.
- Filter directory entries out when iterating list() output before read().
- Construct read paths with explicit filename suffixes to avoid landing on a directory.
- 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
- stat() and check isFile before read().
- Filter list() results by type before reading each entry.
- Always include the filename segment in read paths.
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
- plugin.fs.not_found
- plugin.fs.overwrite_required
- plugin.fs.chunk_too_large
- plugin.fs.path_too_long
- plugin.fs.path_outside_sandbox
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/09b0760ef1aec4cc.
Report an issue: GitHub.