thedotmack/claude-mem · error
ViewerUnavailable
ViewerUnavailable
Error message
Viewer UI not found at any expected location
What it means
ServerViewerRoutes serves the bundled viewer UI; at module load it probes the npm-package ui/ directory and the plugin/ui directory for viewer.html. If neither ships the file, viewerHtmlBytes stays null and GET / responds 503 ViewerUnavailable. API routes are unaffected — only the web viewer is missing.
Source
Thrown at src/server/runtime/ServerViewerRoutes.ts:57
});
} else {
logger.warn('SYSTEM', 'viewer.html not found for server runtime', {
candidates: VIEWER_HTML_CANDIDATE_PATHS,
});
}
export class ServerViewerRoutes implements RouteHandler {
setupRoutes(app: Application): void {
const packageRoot = getPackageRoot();
// Serve static assets from BOTH the npm-package `ui` dir and the plugin
// `plugin/ui` dir, matching the worker's resolution order so the viewer
// loads regardless of which layout the server image ships.
app.use(express.static(path.join(packageRoot, 'ui')));
app.use(express.static(path.join(packageRoot, 'plugin', 'ui')));
app.get('/', (_req: Request, res: Response) => {
if (!viewerHtmlBytes) {
res.status(503).json({ error: 'ViewerUnavailable', message: 'Viewer UI not found at any expected location' });
return;
}
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.send(viewerHtmlBytes);
});
}
// Exposed for tests: did the build ship a viewer.html the server can serve?
static hasViewerHtml(): boolean {
return viewerHtmlBytes !== null;
}
}
View on GitHub (pinned to e2d1df569a)
Solutions
- Build or reinstall the package so viewer.html exists under ui/ or plugin/ui/
- Verify getPackageRoot() resolves to the directory that actually contains the ui folder for your layout
- If you do not need the UI, ignore the 503 — JSON API endpoints still work
- Use ServerViewerRoutes.hasViewerHtml() in smoke tests to assert the artifact shipped
Defensive patterns
Strategy: validation
Validate before calling
async function viewerAvailable(base: string): Promise<boolean> {
const res = await fetch(`${base}/`); // 200 = html, 503 ViewerUnavailable = not shipped
return res.status === 200;
}
if (!(await viewerAvailable(base))) {
console.warn('viewer UI not shipped in this build; API still usable');
} Type guard
function isViewerUnavailable(status: number, body: unknown): body is { error: 'ViewerUnavailable'; message: string } {
return status === 503 && typeof body === 'object' && body !== null && (body as { error?: string }).error === 'ViewerUnavailable';
} Try / catch
const res = await fetch(`${base}/`);
const body = await res.json().catch(() => ({}));
if (isViewerUnavailable(res.status, body)) {
// packaging defect: rebuild/reinstall so ui/viewer.html ships; API endpoints are unaffected
disableUi();
} Prevention
- Assert the ui folder ships in Docker builds (COPY or .dockerignore audit)
- Add ServerViewerRoutes.hasViewerHtml() to post-install smoke tests
- Verify getPackageRoot() resolution in bundled/single-file deployments
When it happens
Trigger: Opening the server root URL on a deployment where viewer.html exists under neither <packageRoot>/ui nor <packageRoot>/plugin/ui — custom Docker images, source checkouts without a viewer build, or getPackageRoot() resolving elsewhere (bundled single-file layouts).
Common situations: Image build copying only server dist and dropping the ui folder; publish/.npmignore rules excluding built assets; running from a bundled artifact where __dirname-based root resolution lands in a temp dir.
Related errors
- Viewer UI not found at any expected location
- Service initializing
- Worker is still initializing, please retry
- Instruction not found
- Service initializing
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/22622fe57840f8d1.
Report an issue: GitHub.