paperclipai/paperclip · error
Failed to serve file
Error message
Failed to serve file
What it means
Returned as HTTP 500 by GET /_plugins/:pluginId/ui/* (server/src/routes/plugin-ui-static.ts:503) when res.sendFile's completion callback reports an error — the stat/symlink/containment checks all passed but the actual stream failed (EACCES, EMFILE, ENOSPC, or the file vanished between stat and send). If headers were already sent the status cannot be changed, so the connection is destroyed instead.
Source
Thrown at server/src/routes/plugin-ui-static.ts:503
res.set("Content-Type", contentType);
}
// Step 9: Set CORS headers (plugin UI may be loaded from different origin in dev)
res.set("Access-Control-Allow-Origin", "*");
// Step 10: Send the file
// The plugin source can live in Git worktrees (e.g. ".worktrees/...").
// `send` defaults to dotfiles:"ignore", which treats dot-directories as
// not found. We already enforce traversal safety above, so allow dot paths.
res.sendFile(resolvedFilePath, { dotfiles: "allow" }, (err) => {
if (err) {
log.error(
{ err, pluginId: plugin.id, filePath: resolvedFilePath },
"plugin-ui-static: error sending file",
);
// Only send error if headers haven't been sent yet
if (!res.headersSent) {
res.status(500).json({ error: "Failed to serve file" });
}
}
});
});
return router;
}
View on GitHub (pinned to 120ae5428f)
Solutions
- Retry the request once — most causes (file swap race, transient EMFILE) are self-healing after the reinstall finishes
- If persistent, check read permissions on the file and every parent directory for the server user
- Raise the process file-descriptor limit (ulimit -n) if EMFILE appears in server logs
- Avoid uninstalling/reinstalling plugins while their UI is being served, or expect transient 500s during that window
Defensive patterns
Strategy: retry
Try / catch
const res = await fetch(url, { cache: "no-store" });
if (res.status === 500) {
await new Promise((r) => setTimeout(r, 500));
const retry = await fetch(url, { cache: "no-store" });
if (retry.ok) return retry;
throw new Error("Plugin asset serve failed twice — plugin may be mid-reinstall");
} Prevention
- Treat a single 500 from /_plugins/:id/ui/* as transient (file swap race) and retry once without cache
- Avoid uninstall/reinstall of plugins while their assets are in active use
- Monitor server logs for 'error sending file' entries to catch permission/fd-limit issues early
When it happens
Trigger: The plugin package is replaced/deleted on disk between the statSync check and the sendFile stream opening (reinstall in flight); the server process lacks read permission on the file itself (execute-only directory traversal rights); file-descriptor exhaustion (EMFILE) under heavy asset traffic; disk/IO errors mid-stream.
Common situations: Concurrent plugin upgrade + active browser tabs loading assets; hardened filesystems where directory access and file access differ; fd limits hit on busy dev servers; antivirus/quarantine removing files mid-request.
Related errors
- Plugin UI is not available (status: ${plugin.status})
- File not found
- Access denied
- ${name} must be a JSON object
- Invalid ${name} JSON: ${err instanceof Error ? err.message :
AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18).
Data as JSON: /api/errors/bd93e469bf792cd0.
Report an issue: GitHub.