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

Thrown by the GET '/' handler (handleViewerUI) when viewerHtmlBytes is null — meaning at boot neither ui/viewer.html nor plugin/ui/viewer.html under the package root existed (resolvedViewerHtmlPath was null). The HTML is read once and cached at module load; a missing build artifact makes every viewer request fail. A boot warning ('viewer.html not found at any expected location') is logged separately.

Source

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

    app.get('/health', this.handleHealth.bind(this));
    app.get('/', this.handleViewerUI.bind(this));
    app.get('/stream', this.handleSSEStream.bind(this));
  }

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

    res.json({
      status: 'ok',
      timestamp: Date.now(),
      activeSessions
    });
  });

  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);
  });

  private handleSSEStream = this.wrapHandler((req: Request, res: Response): void => {
    try {
      this.dbManager.getSessionStore();
    } catch (initError: unknown) {
      if (initError instanceof Error) {
        logger.warn('HTTP', 'SSE stream requested before DB initialization', {}, initError);
      }
      res.status(503).json({ error: 'Service initializing' });
      return;
    }

    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');

View on GitHub (pinned to d768ba3643)

Solutions

  1. Run the build that produces viewer.html (npm run build-and-sync) so it lands in the expected ui/ directory.
  2. Verify the file exists at <packageRoot>/ui/viewer.html or <packageRoot>/plugin/ui/viewer.html.
  3. Check the boot log 'Cached viewer.html at boot' line to confirm the path resolved; if absent, the 'not found' warning lists the candidates checked.
  4. If getPackageRoot() is wrong, ensure the process cwd / package.json location is correct.
Defensive patterns

Strategy: validation

Validate before calling

// At boot, surface the missing artifact rather than failing per-request
import { existsSync } from 'fs';
const ok = VIEWER_HTML_CANDIDATE_PATHS.some(existsSync);
if (!ok) logger.error('SYSTEM', 'viewer.html missing — rebuild the package');

Try / catch

// In the route, degrade gracefully instead of throwing
try {
  if (!viewerHtmlBytes) throw new Error('Viewer UI not found');
  res.send(viewerHtmlBytes);
} catch {
  res.status(503).send('Viewer UI not built. Run npm run build-and-sync.');
}

Prevention

When it happens

Trigger: Any HTTP GET '/' against the viewer server after the package was installed/built without the viewer HTML present in either candidate location.

Common situations: Running from a source tree where the UI build step was skipped; npm package installed without the bundled ui/ directory; plugin/ assembled without copying ui/viewer.html; getPackageRoot() resolving to an unexpected directory.

Related errors


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