thedotmack/claude-mem · error

Observation TV UI not found at any expected location

Error message

Observation TV UI not found at any expected location

What it means

The Observation TV route serves a static tv.html cached into memory at module load from <packageRoot>/ui/tv.html or <packageRoot>/plugin/ui/tv.html. If neither file existed when the worker booted, tvHtmlBytes is null, and hitting the TV endpoint throws this error instead of sending a blank or broken page.

Source

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

    });
  });

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

  /**
   * The target of the restart link in the observer-outage warning
   * (renderObserverHealthWarning). Serves an INERT page: the restart itself is
   * the POST /api/admin/restart behind the button, never this GET.
   *
   * A GET that restarted the worker would fire from any page that could name
   * the URL — `<img src="http://localhost:PORT/restart">` on a site the user
   * happens to open is enough. Same reason the page does not POST on load: an
   * <iframe> would run that script.
   *
   * Requiring a click is NOT on its own enough, though: an attacker who frames
   * this page can still collect a real click through an overlay. So the route
   * also refuses to be framed at all — frame-ancestors 'none' for modern

View on GitHub (pinned to 8bc631a71a)

Solutions

  1. Reinstall the package cleanly: rm -rf node_modules && npm install (or npx claude-mem@latest) so ui/tv.html is restored
  2. Verify the file exists at <packageRoot>/ui/tv.html (or plugin/ui/tv.html) and restart the claude-mem worker
  3. If installing from source, run the build step that copies ui assets into the package root before starting the worker

Example fix

// before
# package missing assets
ls node_modules/claude-mem/ui/   # tv.html absent
// after
rm -rf node_modules package-lock.json && npm install
ls node_modules/claude-mem/ui/tv.html && restart claude-mem worker
Defensive patterns

Strategy: try-catch

Validate before calling

import { existsSync } from 'fs';
if (!['ui/tv.html', 'plugin/ui/tv.html'].some(p => existsSync(require('path').join(packageRoot, p)))) {
  throw new Error('tv.html missing from package; reinstall claude-mem');
}

Try / catch

app.get('/tv', (req, res) => {
  if (!tvHtmlBytes) {
    res.status(503).send('Observation TV assets missing — reinstall claude-mem.');
    return;
  }
  res.type('html').send(tvHtmlBytes);
});

Prevention

When it happens

Trigger: Requesting the Observation TV endpoint (handleTvUI route) from a worker whose package install is missing ui/tv.html and plugin/ui/tv.html — typically a broken/partial npm install or a stale published package built before the file was added.

Common situations: Corrupted or interrupted npm install leaving assets behind; running from a dev checkout where ui assets were not built/copied; upgrading claude-mem while an old worker process (without tv.html support) is still serving.

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@8bc631a71a (2026-09-09). Data as JSON: /api/errors/671067562d0cb236. Report an issue: GitHub.