nanocoai/nanoclaw · error
Plugin rejected: "${rel}" is not a regular file or directory
Error message
Plugin rejected: "${rel}" is not a regular file or directory What it means
Thrown by the plugin directory walker when a directory entry is neither a directory nor a regular file. This happens for symlinks (entry.isFile() is false because isFile uses stat semantics only for regular files — here the code checks Dirent type), FIFOs, sockets, or device nodes. The library enforces that plugin payloads contain only regular files and directories.
Source
Thrown at src/templates/plugin-dir.ts:74
}
const rel = relDir ? `${relDir}/${name}` : name;
const abs = path.join(absDir, name);
if (entry.isSymbolicLink()) {
throw new Error(`Plugin rejected: "${rel}" is a symlink (symlinks are not allowed in plugins)`);
}
if (depth + 1 > MAX_PLUGIN_DEPTH) {
throw new Error(`Plugin rejected: "${rel}" exceeds the maximum nesting depth of ${MAX_PLUGIN_DEPTH}`);
}
entries += 1;
if (entries > MAX_PLUGIN_FILES) {
throw new Error(`Plugin rejected: more than ${MAX_PLUGIN_FILES} entries`);
}
if (entry.isDirectory()) {
visit(rel, depth + 1);
continue;
}
if (!entry.isFile()) {
throw new Error(`Plugin rejected: "${rel}" is not a regular file or directory`);
}
const stat = fs.lstatSync(abs);
files.push({ rel, abs, size: stat.size, mode: stat.mode });
totalBytes += stat.size;
if (totalBytes > MAX_PLUGIN_TOTAL_BYTES) {
throw new Error(`Plugin rejected: total size exceeds ${MAX_PLUGIN_TOTAL_BYTES} bytes`);
}
}
};
visit('', 0);
return files;
}
/**
* Copy a validated plugin tree. `dest` is replaced wholesale so re-stamping
* is idempotent. Re-walks `src` at copy time so the validation and the copy
* see the same tree.View on GitHub (pinned to 294ef2aee8)
Solutions
- Remove or replace non-regular entries (symlinks, sockets, FIFOs) in the plugin directory with real files or copy their targets
- If a symlink is needed, copy the target file's contents into the plugin dir instead
- Check for stray files: find plugins/<name> -type s -o -type p -o -type l
Example fix
# before plugins/my-plugin/config.json -> ../../shared/config.json (symlink) # after cp ../../shared/config.json plugins/my-plugin/config.json
Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs');
const path = require('path');
function assertRegularFiles(dir) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const abs = path.join(dir, e.name);
if (e.isDirectory()) assertRegularFiles(abs);
else if (!e.isFile()) throw new Error(`non-regular entry: ${abs} (${e.isSymbolicLink() ? 'symlink' : 'other'})`);
}
}
// assertRegularFiles(pluginDir) before walkPluginDir Try / catch
try { walkPluginDir(dir); } catch (e) { if (/not a regular file or directory/.test(e.message)) { /* surface offending path from message, fix tree */ } throw e; } Prevention
- Never commit symlinks inside plugin directories
- Add a lint step to your template build that rejects non-regular files
- Clean dev artifacts (sockets, temp files) before packaging
When it happens
Trigger: Calling walkPluginDir on a plugin folder that contains a Unix socket, FIFO, device file, or a symlink to a file (Dirent.isFile() returns false for symlinks). Any lstat-derived Dirent that is not DIRECTORY or FILE triggers it.
Common situations: A plugin dir containing a symlink (e.g. node_modules-style links or a shared config symlink), or a leftover socket file from a dev process inside the copied plugin folder.
Related errors
- engage_mode '${w.engage_mode}' can never engage on channel '
- chat-sdk bridge instance ${JSON.stringify(config.instance)}
- invalid task id: ${series}
- ${NANOCLAW_EXTENSION_NS}/context/instructions.md must be a r
- Could not read group standing instructions; omitting persona
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/bcaa21a59a29ed02.
Report an issue: GitHub.