paperclipai/paperclip · error
Plugin UI directory not found
Error message
Plugin UI directory not found
What it means
Returned as HTTP 404 by GET /_plugins/:pluginId/ui/* (server/src/routes/plugin-ui-static.ts:420) when resolvePluginUiDir() cannot find the plugin's UI directory on disk. The resolver tries the persisted packagePath, then <localPluginDir>/node_modules/<packageName>, then <localPluginDir>/<packageName>, and finally <packageRoot>/<entrypoints.ui>; if none exists the route logs 'UI directory not found on disk' and 404s even though the plugin is 'ready' and declares a UI.
Source
Thrown at server/src/routes/plugin-ui-static.ts:420
}
} catch {
// Config lookup failure is non-fatal — fall through to static serving
}
// Step 3: Resolve the plugin's UI directory
const uiDir = resolvePluginUiDir(
options.localPluginDir,
plugin.packageName,
manifest.entrypoints.ui,
plugin.packagePath,
);
if (!uiDir) {
log.warn(
{ pluginId: plugin.id, pluginKey: plugin.pluginKey, packageName: plugin.packageName },
"plugin-ui-static: UI directory not found on disk",
);
res.status(404).json({ error: "Plugin UI directory not found" });
return;
}
// Step 4: Resolve the requested file path and prevent traversal (including symlinks)
const resolvedFilePath = path.resolve(uiDir, rawFilePath);
// Step 5: Check that the file exists and is a regular file
let fileStat: fs.Stats;
try {
fileStat = fs.statSync(resolvedFilePath);
} catch {
res.status(404).json({ error: "File not found" });
return;
}
// Security: resolve symlinks via realpathSync and verify containment.
// This prevents symlink-based traversal that string-based startsWith misses.
let realFilePath: string;View on GitHub (pinned to 120ae5428f)
Solutions
- Verify the UI directory physically exists: ls <localPluginDir>/node_modules/<packageName>/dist/ui (or the packagePath location)
- If missing, rebuild the plugin (npm run build producing dist/ui) and reinstall it so the registry re-points at a valid package
- Confirm the server's localPluginDir option matches the directory used at install time (default ~/.paperclip/plugins/)
- For local-path installs, make sure the plugin's packagePath still exists on this machine and contains the entrypoints.ui directory
Example fix
# before: package.json of the plugin publishes without building
"files": ["src"]
# after: include built UI output and build before publish
"scripts": { "prepublishOnly": "npm run build" },
"files": ["dist"] Defensive patterns
Strategy: validation
Validate before calling
import fs from "node:fs";
import path from "node:path";
// Run after install, before marking the plugin ready
const uiDir = path.join(packageRoot, manifest.entrypoints.ui);
if (!fs.existsSync(uiDir)) {
throw new Error(`UI bundle missing: ${uiDir} — run the plugin build before install`);
} Prevention
- Build the plugin UI (dist/ui) before install/publish and include it in the package files list
- Start the server with the same localPluginDir used by the installer
- For local-path installs, keep the source directory in place or update the plugin's packagePath
When it happens
Trigger: Plugin marked ready and manifest declares entrypoints.ui='./dist/ui/', but the installed package has no dist/ui directory (build step skipped, .npmignore excluded it), the package was deleted from ~/.paperclip/plugins/node_modules, packagePath points to a moved/deleted local directory, or the server was started with a localPluginDir option different from the directory the plugin was installed into.
Common situations: Plugin published without building the UI bundle first; local-path installs after the source directory was moved or the worktree deleted; running multiple server instances with different plugin dirs; partial/corrupted npm installs during plugin upgrade.
Related errors
- Plugin does not declare a UI bundle
- File not found
- Plugin UI is not available (status: ${plugin.status})
- Job not found
- ${name} must be a JSON object
AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18).
Data as JSON: /api/errors/d04b278eab8450cf.
Report an issue: GitHub.