mihomo-party-org/clash-party · error · Error
Plugin path is not a file
Error message
Plugin path is not a file
What it means
After resolving the path, readPluginFile opens it and stats it; if stat.isFile() is false (a directory, FIFO, device, etc.) it throws 'Plugin path is not a file'. The loader needs a regular file to read bytes from, so anything else is rejected.
Source
Thrown at src/main/resolve/plugin/file.ts:18
import { open } from 'fs/promises'
import { basename, extname, resolve } from 'path'
import { MAX_PLUGIN_FILE_BYTES } from './constants'
export function findPluginFile(args: string[]): string | undefined {
return args.find(
(arg) => !arg.startsWith('-') && !arg.includes('://') && extname(arg).toLowerCase() === '.cpx'
)
}
export async function readPluginFile(filePath: string): Promise<IPluginFilePayload> {
if (extname(filePath).toLowerCase() !== '.cpx') throw new Error('Unsupported plugin file type')
const resolvedPath = resolve(filePath)
const handle = await open(resolvedPath, 'r')
try {
const stat = await handle.stat()
if (!stat.isFile()) throw new Error('Plugin path is not a file')
if (stat.size > MAX_PLUGIN_FILE_BYTES) throw new Error('Plugin file too large')
// Read at most one byte beyond the limit so a file growing after stat cannot bypass the cap.
const bytes = Buffer.alloc(MAX_PLUGIN_FILE_BYTES + 1)
let length = 0
while (length < bytes.length) {
const { bytesRead } = await handle.read(bytes, length, bytes.length - length, null)
if (bytesRead === 0) break
length += bytesRead
}
if (length > MAX_PLUGIN_FILE_BYTES) throw new Error('Plugin file too large')
return {
name: basename(resolvedPath),
fileBytesB64: bytes.subarray(0, length).toString('base64')
}
} finally {
await handle.close()View on GitHub (pinned to 911e090537)
Solutions
- Point the path at the .cpx file itself, not its containing or extracted directory.
- If a symlink is involved, confirm it resolves to a regular file (ls -l / stat).
- Re-download the plugin package if the local file was partially extracted or corrupted.
Example fix
// before
await readPluginFile('/plugins/my-plugin.cpx/') // directory
// after
await readPluginFile('/plugins/my-plugin.cpx') // regular file Defensive patterns
Strategy: validation
Validate before calling
import { statSync } from 'node:fs'
const st = statSync(pluginPath)
if (!st.isFile()) {
throw new Error(`Plugin path must be a regular file, got: ${pluginPath}`)
} Type guard
function isRegularFile(p: string): boolean {
try { return require('node:fs').statSync(p).isFile() } catch { return false }
} Try / catch
try {
const payload = await readPluginFile(pluginPath)
} catch (e) {
if ((e as Error).message === 'Plugin path is not a file') {
// tell the user to point at the .cpx file itself
} else throw e
} Prevention
- Store the full path to the .cpx file in config, never a directory.
- Check symlinks resolve to regular files (stat follows symlinks).
- Wrap path setup in an isFile check during startup/config validation.
When it happens
Trigger: Passing a directory path or a special file (socket/FIFO/device node) to readPluginFile via payload. Note the file may have the right .cpx extension but be a directory (e.g. 'my-plugin.cpx/').
Common situations: Configured plugin path points at an extracted folder instead of the package, a mount point, or a broken symlink resolving to a directory.
Related errors
- Unsupported plugin file type
- Plugin file too large
- Plugin file too large
- Invalid plugin URL
- Plugin URL must not contain a fragment
AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30).
Data as JSON: /api/errors/4757d71ecc1bb140.
Report an issue: GitHub.