paperclipai/paperclip · warning
[paperclip] UI dist not found; running in API-only mode
Error message
[paperclip] UI dist not found; running in API-only mode
What it means
When the API server starts in static-serving mode (production), it scans candidate directories for a built UI dist containing index.html. If none is found, it warns and serves only /api routes — the board UI is unreachable in the browser while the REST API remains fully functional.
Source
Thrown at server/src/app.ts:716
// SPA fallback. Only for non-asset routes — if the browser asks for
// /assets/something.js that doesn't exist, we must NOT serve the HTML
// shell: the browser would try to load it as a JavaScript module, fail
// with a MIME-type error, and cache that broken response. Return 404
// instead. The index.html response itself is no-cache so a subsequent
// deploy's updated asset hashes are picked up on next load.
app.get(/.*/, (req, res) => {
if (req.path.startsWith("/assets/")) {
res.status(404).end();
return;
}
res
.status(200)
.set("Content-Type", "text/html")
.set("Cache-Control", "no-cache")
.end(readBrandedStaticIndexHtml(uiDist));
});
} else {
console.warn("[paperclip] UI dist not found; running in API-only mode");
}
}
if (opts.uiMode === "vite-dev") {
const uiRoot = path.resolve(__dirname, "../../ui");
const publicUiRoot = path.resolve(uiRoot, "public");
const hmrPort = resolveViteHmrPort(opts.serverPort);
const hmrHost = resolveViteHmrHost(opts.bindHost);
const hmrProtocol = resolveViteHmrProtocol(process.env.PAPERCLIP_VITE_HMR_PROTOCOL);
const hmrServer = createHttpServer((_req, res) => {
res.writeHead(426, { "Content-Type": "text/plain" });
res.end("Upgrade Required");
});
const { createServer: createViteServer } = await import("vite");
const vite = await createViteServer({
root: uiRoot,
appType: "custom",
server: {View on GitHub (pinned to a7e689b3c3)
Solutions
- Build the UI first: run the repo build (`pnpm build`) so ui/dist with index.html exists next to the server.
- Verify the directory the server scans actually contains index.html (candidates are resolved relative to the server build output).
- For local development use vite-dev mode (`pnpm dev`) instead of static mode.
- If API-only operation is intended, ignore the warning and interact via REST/CLI.
Example fix
# before pnpm --filter server start # after pnpm build && pnpm start
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
import path from 'node:path';
// Fail fast at deploy time instead of silently serving API-only.
const uiDist = path.resolve(__dirname, '../../ui/dist');
if (!fs.existsSync(path.join(uiDist, 'index.html'))) {
throw new Error(`UI dist missing at ${uiDist}; run pnpm build before starting in static mode`);
} Prevention
- Build UI and server in the same CI artifact; assert index.html exists in the package.
- Add a post-deploy smoke check that GET / returns HTML (not just /api/health).
- Use vite-dev mode only for local dev; static mode requires a prior build.
- Alert on this warn in production logs — it means the board UI is down.
When it happens
Trigger: Starting the server in static mode without a prior UI build (ui/dist absent); a packaged deployment that shipped only the server; the dist directory moved or renamed so no candidate contains index.html.
Common situations: Running the production entrypoint where only `pnpm dev` was ever used; Docker image built from server-only context; CI artifact missing the UI build; deploying to a host where the relative path to ui/dist changed.
Related errors
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-18).
Data as JSON: /api/errors/b7dd9680fd9342a1.
Report an issue: GitHub.