thedotmack/claude-mem · error · Error

Viewer UI not found at any expected location

Error message

Viewer UI not found at any expected location

What it means

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.

Solutions

  1. Rebuild/reinstall the package so ui/viewer.html exists (npm run build-and-sync per project docs)
  2. Verify the file exists at packageRoot/ui/viewer.html or packageRoot/plugin/ui/viewer.html where getPackageRoot() points
  3. Check the 'Cached viewer.html at boot' vs missing-file log line at startup to see which root was resolved
  4. If serving from source, run the sync step that copies plugin assets to the marketplace/install location

Example fix

// before
const packageRoot = getPackageRoot(); // resolves to a dir without ui/viewer.html
// after
// rebuild & sync assets first:
//   npm run build-and-sync
// then confirm:
//   ls $(node -e "console.log(require('./src/shared/paths.js').getPackageRoot())")/ui/viewer.html
Defensive patterns

Strategy: fallback

Validate before calling

import { existsSync } from 'fs';
import path from 'path';
const packageRoot = getPackageRoot();
const viewerHtmlExists =
  existsSync(path.join(packageRoot, 'ui', 'viewer.html')) ||
  existsSync(path.join(packageRoot, 'plugin', 'ui', 'viewer.html'));

Try / catch

app.get('/viewer', (req, res) => {
  try {
    return viewerHandler(req, res);
  } catch (err) {
    if (err instanceof Error && err.message.includes('Viewer UI not found')) {
      return res.status(503).send('Viewer UI asset missing — rebuild/reinstall the package.');
    }
    throw err;
  }
});

Prevention

When it happens

Trigger: 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/.

Common situations: 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.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/1701c946d39625ca. Report an issue: GitHub.

Appendix: source

Thrown at src/services/worker/http/routes/ViewerRoutes.ts:183

  }

  private handleHealth = this.wrapHandler((req: Request, res: Response): void => {
    const activeSessions = this.sessionManager.getActiveSessionCount();

    res.json({
      status: 'ok',
      timestamp: Date.now(),
      activeSessions,
      // Per-process identity, so a caller waiting out a restart can tell the
      // successor from the worker it just asked to die. The dying worker keeps
      // answering here for the whole graceful-shutdown window.
      pid: process.pid,
    });
  });

  private handleViewerUI = this.wrapHandler((req: Request, res: Response): void => {
    if (!viewerHtmlBytes) {
      throw new Error('Viewer UI not found at any expected location');
    }
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.send(viewerHtmlBytes);
  });

  /**
   * Observation TV: the same /stream the viewer consumes, rendered as a
   * full-screen fading title card. Static, dependency-free, and served from
   * the same origin so the EventSource needs no CORS of its own.
   */
  private handleTvUI = this.wrapHandler((req: Request, res: Response): void => {
    if (!tvHtmlBytes) {
      throw new Error('Observation TV UI not found at any expected location');
    }
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.send(tvHtmlBytes);
  });

View on GitHub (pinned to d8bc9755e7)