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

  1. Build the UI first: run the repo build (`pnpm build`) so ui/dist with index.html exists next to the server.
  2. Verify the directory the server scans actually contains index.html (candidates are resolved relative to the server build output).
  3. For local development use vite-dev mode (`pnpm dev`) instead of static mode.
  4. 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

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.