diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md index 647a319f..12ddbdab 100644 --- a/docs/PLUGINS.md +++ b/docs/PLUGINS.md @@ -149,7 +149,7 @@ output goes to `client-build.log` next to it; the server's own output goes to |---|---| | `/ccam-doctor` | Node version, bootstrap state, runtime deps, server liveness, duplicate hooks, CLI launcher + PATH, MCP build freshness, UI bundle | | `/ccam-update` | Pull the latest `ccam` release via `claude plugin marketplace update` + `claude plugin update ccam@ -y`, then reinstall dependencies and restart the server (`plugin-bootstrap.js --force`). The plugin pull needs a manual `/reload-plugins` afterward to take effect in the current session — not scriptable from a command. | -| `/ccam-open` | Build the UI bundle if missing, then print the dashboard URL | +| `/ccam-open` | Rebuild the UI bundle and print the dashboard URL. Re-runs the bootstrap first when the server is down or still executing a plugin root that a mid-session `/plugin` update retired, so a fresh bundle never talks to an older server's routes | ### Where things live diff --git a/plugins/ccam/commands/ccam-open.md b/plugins/ccam/commands/ccam-open.md index 82f00ada..6c24c4a6 100644 --- a/plugins/ccam/commands/ccam-open.md +++ b/plugins/ccam/commands/ccam-open.md @@ -12,8 +12,12 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/plugin-open.js" --force ``` Install + build takes a few minutes if the client toolchain isn't already -installed; otherwise the rebuild itself takes ~10-15s. No server restart is -needed — the server already serves from that directory. +installed; otherwise the rebuild itself takes ~10-15s. The server serves from +that directory, so it usually needs no restart — but if it is down, or still +running from a plugin root a mid-session `/plugin` update retired, the command +re-runs bootstrap first and prints `Server restarted on the current plugin +version.` (without it, the fresh bundle would call routes that old server +never had). Then help the user open it: diff --git a/scripts/plugin-open.js b/scripts/plugin-open.js index 4c6176a6..3bf6af93 100644 --- a/scripts/plugin-open.js +++ b/scripts/plugin-open.js @@ -6,7 +6,10 @@ * on session start, so client-only routes (e.g. `/run`) work the moment * `claude` is started, not just the API and MCP tools. `/ccam-open` re-runs * this standalone — a no-op when the bundle already matches — as a fallback - * for `--force` rebuilds or a first build that failed during bootstrap. + * for `--force` rebuilds or a first build that failed during bootstrap, and it + * re-runs bootstrap first when the live server belongs to a retired plugin root + * (a mid-session `/plugin` update) so the new bundle is not left calling routes + * the running server never had. * * The client source is copied out of the (replaced-on-update) plugin cache into * the runtime dir before building, so neither the cache nor the checkout is @@ -74,12 +77,21 @@ function dashboardUrl() { } if (require.main === module) { + // A plugin update applied mid-session (/plugin + /reload-plugins) fires no + // SessionStart, so the server keeps executing a cache dir Claude Code has + // already retired: a bundle rebuilt from the new root then calls routes that + // old server never had. Hand the version handover back to bootstrap, which + // owns stop/start and the dependency install the new root may need. + const stale = (boot.readState() || {}).pluginRoot !== PLUGIN_ROOT || !boot.serverIsLive(); + if (stale) boot.bootstrap(); + const result = buildClient({ force: process.argv.includes("--force") }); if (result === "failed") { console.error("Client build failed — the API and MCP tools still work."); process.exitCode = 1; } else { if (result === "built") console.log("Dashboard UI built."); + if (stale) console.log("Server restarted on the current plugin version."); console.log(dashboardUrl()); } }