{"record":{"id":"1701c946d39625ca","repo":"thedotmack/claude-mem","slug":"viewer-ui-not-found-at-any-expected-location","errorCode":null,"errorMessage":"Viewer UI not found at any expected location","messagePattern":"Viewer UI not found at any expected location","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/worker/http/routes/ViewerRoutes.ts","lineNumber":183,"sourceCode":"  }\n\n  private handleHealth = this.wrapHandler((req: Request, res: Response): void => {\n    const activeSessions = this.sessionManager.getActiveSessionCount();\n\n    res.json({\n      status: 'ok',\n      timestamp: Date.now(),\n      activeSessions,\n      // Per-process identity, so a caller waiting out a restart can tell the\n      // successor from the worker it just asked to die. The dying worker keeps\n      // answering here for the whole graceful-shutdown window.\n      pid: process.pid,\n    });\n  });\n\n  private handleViewerUI = this.wrapHandler((req: Request, res: Response): void => {\n    if (!viewerHtmlBytes) {\n      throw new Error('Viewer UI not found at any expected location');\n    }\n    res.setHeader('Content-Type', 'text/html; charset=utf-8');\n    res.send(viewerHtmlBytes);\n  });\n\n  /**\n   * Observation TV: the same /stream the viewer consumes, rendered as a\n   * full-screen fading title card. Static, dependency-free, and served from\n   * the same origin so the EventSource needs no CORS of its own.\n   */\n  private handleTvUI = this.wrapHandler((req: Request, res: Response): void => {\n    if (!tvHtmlBytes) {\n      throw new Error('Observation TV UI not found at any expected location');\n    }\n    res.setHeader('Content-Type', 'text/html; charset=utf-8');\n    res.send(tvHtmlBytes);\n  });\n","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/worker/http/routes/ViewerRoutes.ts#L165-L201","documentation":"The GET handler serving the viewer UI throws when viewerHtmlBytes is null — i.e. viewer.html was not found at any of the candidate paths (packageRoot/ui/viewer.html or packageRoot/plugin/ui/viewer.html) at module load. The routes module reads the file once at boot and caches the bytes, so a missing file at startup means this error on every UI request.","triggerScenarios":"GET to the viewer UI endpoint when the plugin package was installed without the ui/viewer.html asset, the wrong package root was resolved, or a dev build skipped copying the UI files into plugin/ui/.","commonSituations":"Partial or corrupted npm install; running from source without running the build step that emits ui/viewer.html; getPackageRoot() resolving to a different directory after a version change or monorepo layout change; deployment pipeline that strips .html assets.","solutions":["Rebuild/reinstall the package so ui/viewer.html exists (npm run build-and-sync per project docs)","Verify the file exists at packageRoot/ui/viewer.html or packageRoot/plugin/ui/viewer.html where getPackageRoot() points","Check the 'Cached viewer.html at boot' vs missing-file log line at startup to see which root was resolved","If serving from source, run the sync step that copies plugin assets to the marketplace/install location"],"exampleFix":"// before\nconst packageRoot = getPackageRoot(); // resolves to a dir without ui/viewer.html\n// after\n// rebuild & sync assets first:\n//   npm run build-and-sync\n// then confirm:\n//   ls $(node -e \"console.log(require('./src/shared/paths.js').getPackageRoot())\")/ui/viewer.html","handlingStrategy":"fallback","validationCode":"import { existsSync } from 'fs';\nimport path from 'path';\nconst packageRoot = getPackageRoot();\nconst viewerHtmlExists =\n  existsSync(path.join(packageRoot, 'ui', 'viewer.html')) ||\n  existsSync(path.join(packageRoot, 'plugin', 'ui', 'viewer.html'));","typeGuard":null,"tryCatchPattern":"app.get('/viewer', (req, res) => {\n  try {\n    return viewerHandler(req, res);\n  } catch (err) {\n    if (err instanceof Error && err.message.includes('Viewer UI not found')) {\n      return res.status(503).send('Viewer UI asset missing — rebuild/reinstall the package.');\n    }\n    throw err;\n  }\n});","preventionTips":["Always run the full build/sync step after installing or updating the plugin","Verify ui/viewer.html exists in your deployment artifact before starting the worker","Check startup logs for the 'Cached viewer.html at boot' line as a health signal","Pin package installs (lockfile) to avoid partial installs stripping assets"],"tags":["http","static-assets","deployment"],"backgroundTag":"file-not-found","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}