Skip to content

Theme Switcher 404s on stale SSH backends; web dashboard UI is not shipped #2

Description

@jdtimothy
Image

Bug description

The Theme Switcher UI is visible in Hermes Desktop, but its first ctx.rest('/list') call can fail with a 404 when Desktop is connected to a Linux Hermes backend over SSH:

Error invoking remote method 'hermes:api': Error: 404: {"error":"Headless backend (hermes serve): web UI disabled — use `hermes dashboard` for the browser UI."} — the theme-switcher backend mounts when the app starts. Restart the app if this persists.

The same pack also does not provide a working web dashboard Themes tab. That appears to be a separate delivery mismatch: this repo ships a native Desktop ESM plugin, but no web-dashboard UI bundle.

Environment

  • Theme pack: current main, commit fd718829bd9b8c7bf0846bf44bdd59bfdf3d0068
  • Hermes Agent: v0.20.0 (2026.8.3)
  • Backend host: Linux
  • Client: Hermes Desktop on Windows, connected to the Linux backend over SSH
  • Backend plugin is installed under ~/.hermes/plugins/theme-switcher/ and listed as enabled
  • Desktop plugin is installed in the Windows-local desktop-plugins/theme-switcher/plugin.js directory

Reproduction

  1. Start/connect Hermes Desktop to a remote Linux machine over SSH. This launches hermes serve --isolated --port 0 ... on the remote machine.
  2. Install and enable the theme-switcher backend after that headless process is already running.
  3. Install/reload the Desktop plugin.js locally on Windows.
  4. Open Themes.
  5. The /api/plugins/theme-switcher/list request gets the 404 above.

For the browser dashboard case:

  1. Run hermes dashboard.
  2. Install this repo as documented.
  3. There is no web-dashboard Theme Switcher implementation to load.

Root-cause evidence

This is not a malformed route in plugin.js: ctx.rest('/list') correctly resolves to /api/plugins/theme-switcher/list.

Hermes mounts Python plugin routers only once, during web_server.py module/app startup (_mount_plugin_api_routes()), before the SPA/headless catch-all. Enabling the plugin later does not add its router to already-running hermes serve processes.

On the affected machine I found three SSH-owned headless processes that all predated the plugin enable/install step. Their OpenAPI documents had no theme-switcher routes:

/api/plugins/theme-switcher/*  -> absent

A fresh temporary hermes serve launched after the plugin was enabled exposed:

/api/plugins/theme-switcher/apply
/api/plugins/theme-switcher/install
/api/plugins/theme-switcher/list
/api/plugins/theme-switcher/raw
/api/plugins/theme-switcher/settings

and an authenticated request to /api/plugins/theme-switcher/list returned HTTP 200.

The standalone hermes dashboard process also mounted these routes successfully after a fresh start. So the SSH failure is specifically a stale backend process, not the Python router implementation.

The current UI error is misleading because it says the backend “mounts when the app starts.” More precisely, it mounts when the backend server process starts. Reloading the Desktop plugin, reloading the window, or retrying cannot repair an already-running SSH backend whose FastAPI app was created before enablement.

Web dashboard is a different plugin system

Hermes documents the native Desktop SDK and web Dashboard SDK as unrelated:

  • Desktop: @hermes/plugin-sdk, local desktop-plugins/<id>/plugin.js
  • Web dashboard: window.__HERMES_PLUGIN_SDK__, dashboard/manifest.json, and a prebuilt IIFE entry

This repo's plugins/theme-switcher/dashboard/manifest.json contains only:

{ "name": "theme-switcher", "api": "plugin_api.py" }

There is no entry, tab, or dashboard/dist/index.js, so it installs only the shared backend API—not a web-dashboard tab. The README phrase “Theme Switcher (desktop browser)” is easy to read as browser-dashboard support even though the implementation is Desktop-only.

Suggested fixes

  1. Make installation atomic and correctly ordered. Have install.sh install the skins, backend plugin, enable it, and install/copy the Desktop plugin (or clearly split local-client vs remote-backend steps). Right now ./install.sh installs only skins, while the backend/UI steps are separate manual commands.
  2. Detect stale backend processes. After enabling the backend, print an explicit instruction to restart/reconnect the remote hermes serve backend—not merely reload/restart the renderer. In SSH mode, closing/reopening the UI may reuse or leave old remote serve processes, so the instruction should name the backend lifecycle.
  3. Improve the error message. On a 404 for /list, say that the backend route is absent and the connected Hermes backend must be restarted after enabling the plugin. Do not imply Retry or Desktop hot reload can mount it.
  4. Clarify web support. Either:
    • document prominently that the Themes page is native-Hermes-Desktop-only and that the web dashboard only receives the backend API; or
    • ship a separate web-dashboard UI bundle using window.__HERMES_PLUGIN_SDK__, with tab + entry in dashboard/manifest.json.
  5. Add an integration test. In a temporary HERMES_HOME, install + enable the plugin, start a fresh headless server, and assert /api/plugins/theme-switcher/list returns 200. If web support is intended, also validate that the dashboard manifest has a real entry bundle.

Expected behavior

  • Following the documented install path results in a working Themes page without needing to infer that a remote backend process—not just the app window—must restart.
  • The README clearly distinguishes native Desktop support from browser-dashboard support.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions