{"record":{"id":"b7dd9680fd9342a1","repo":"paperclipai/paperclip","slug":"paperclip-ui-dist-not-found-running-in-api-only","errorCode":null,"errorMessage":"[paperclip] UI dist not found; running in API-only mode","messagePattern":"\\[paperclip\\] UI dist not found; running in API-only mode","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/src/app.ts","lineNumber":977,"sourceCode":"      // never change once built, so they can be cached aggressively.\n      app.use(\n        \"/assets\",\n        express.static(path.join(uiDist, \"assets\"), {\n          maxAge: \"1y\",\n          immutable: true,\n        }),\n      );\n      // Serve root/index through the same runtime HTML transform as SPA routes.\n      app.get([\"/\", \"/index.html\"], (_req, res) => {\n        res.type(\"html\").set(\"Cache-Control\", \"no-cache\").send(readBrandedStaticIndexHtml(uiDist));\n      });\n      // Non-hashed static files (favicon.ico, manifest, robots.txt, etc.):\n      // short cache so operators who swap them out see the new version\n      // reasonably fast, with must-revalidate overrides for index.html and\n      // sw.js (see staticUiCacheControl for why those two).\n      app.use(\n        express.static(uiDist, {\n          maxAge: \"1h\",\n          setHeaders(res, filePath) {\n            const override = staticUiCacheControl(filePath);\n            if (override) {\n              res.set(\"Cache-Control\", override);\n            }\n          },\n        }),\n      );\n      // SPA fallback. Only for non-asset routes — if the browser asks for\n      // /assets/something.js that doesn't exist, we must NOT serve the HTML\n      // shell: the browser would try to load it as a JavaScript module, fail\n      // with a MIME-type error, and cache that broken response. Return 404\n      // instead. The index.html response itself is no-cache so a subsequent\n      // deploy's updated asset hashes are picked up on next load.\n      app.get(/.*/, (req, res) => {\n        if (req.path.startsWith(\"/assets/\")) {\n          res.status(404).end();\n          return;","sourceCodeStart":959,"sourceCodeEnd":995,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/app.ts#L959-L995","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\npnpm --filter server start\n\n# after\npnpm build && pnpm start","handlingStrategy":"validation","validationCode":"import fs from 'node:fs';\nimport path from 'node:path';\n\n// Fail fast at deploy time instead of silently serving API-only.\nconst uiDist = path.resolve(__dirname, '../../ui/dist');\nif (!fs.existsSync(path.join(uiDist, 'index.html'))) {\n  throw new Error(`UI dist missing at ${uiDist}; run pnpm build before starting in static mode`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["ui","build-artifact","static-serving","deployment"],"backgroundTag":"missing-build-artifact","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}