From c017f6dad9963e3ca6569c21b23036f6217bf407 Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Wed, 26 Aug 2026 10:57:03 +0700 Subject: [PATCH] fix(plugin): restart a server left on a retired plugin root A plugin update applied mid-session (/plugin followed by /reload-plugins) fires no SessionStart, so the bootstrap's version handover never runs and the dashboard server keeps executing a cache directory Claude Code has already discarded. /ccam-open then rebuilt the bundle from the new root against that old server, and the new bundle called routes the old server never had: /api/lanes/pipeline-stats fell through to /:id and rendered "lane not found", /api/workflows/skill-usage fell through to the SPA catch-all and broke JSON.parse on the returned HTML. Both Workflows panels showed an error while the code they needed was sitting on disk. plugin-open now re-runs bootstrap when the live server's recorded pluginRoot differs from the current one, or when no server is live at all. Bootstrap already owns the stop/start and the dependency install the new root may need, so this only restores the trigger that a mid-session update skips. --- docs/PLUGINS.md | 2 +- plugins/ccam/commands/ccam-open.md | 8 ++++++-- scripts/plugin-open.js | 14 +++++++++++++- 3 files changed, 20 insertions(+), 4 deletions(-) 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()); } }