From aa8ac6cad92d064e395960dd45be254d86592c1e Mon Sep 17 00:00:00 2001
From: nntrivi2001
Date: Fri, 16 Oct 2026 14:54:43 +0700
Subject: [PATCH] =?UTF-8?q?fix:=20v2.8.10=20=E2=80=94=20the=20server=20sta?=
=?UTF-8?q?rts=20again=20on=20plugin=20installs=20(js-yaml=20is=20a=20runt?=
=?UTF-8?q?ime=20dependency)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
v2.8.9 crashed at start on every plugin install with "Cannot find module
'js-yaml'": server/lib/lane-detect.js requires it, but it was only a
devDependency that redoc used to pull in, and v2.8.8 dropped redoc. The
plugin installs with --omit=dev. This box hid it: Debian's global
/usr/share/nodejs has js-yaml. Reproduced with a pruned install and the
global paths off; fixed by moving js-yaml (and argparse in the lock) to
dependencies. New runtime-deps.test.js fails on any server/ require that is
not a dependency.
---
.../screens.snapshot.test.tsx.snap | 2 +-
openapi.yaml | 2 +-
package-lock.json | 8 +--
package.json | 4 +-
server/__tests__/runtime-deps.test.js | 68 +++++++++++++++++++
5 files changed, 75 insertions(+), 9 deletions(-)
create mode 100644 server/__tests__/runtime-deps.test.js
diff --git a/client/src/pages/__tests__/__snapshots__/screens.snapshot.test.tsx.snap b/client/src/pages/__tests__/__snapshots__/screens.snapshot.test.tsx.snap
index d69f9f03..e242e399 100644
--- a/client/src/pages/__tests__/__snapshots__/screens.snapshot.test.tsx.snap
+++ b/client/src/pages/__tests__/__snapshots__/screens.snapshot.test.tsx.snap
@@ -7239,7 +7239,7 @@ exports[`screen snapshots > Settings — about 1`] = `
- UI build v2.8.9 (rebuild client to match)
+ UI build v2.8.10 (rebuild client to match)
diff --git a/openapi.yaml b/openapi.yaml
index a040a083..5077b430 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -3,7 +3,7 @@
openapi: 3.0.3
info:
title: Agent Dashboard for Claude Code API
- version: 2.8.9
+ version: 2.8.10
description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence.
contact:
name: Nguyễn Ngọc Trí Vĩ
diff --git a/package-lock.json b/package-lock.json
index 043cff9b..bacd86f0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "agenthub",
- "version": "2.8.9",
+ "version": "2.8.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "agenthub",
- "version": "2.8.9",
+ "version": "2.8.10",
"hasInstallScript": true,
"license": "UNLICENSED",
"dependencies": {
@@ -14,6 +14,7 @@
"adm-zip": "^0.6.1",
"cors": "^2.8.5",
"express": "^4.21.2",
+ "js-yaml": "^4.1.0",
"multer": "^2.0.0",
"swagger-ui-express": "^5.0.1",
"systray2": "^2.1.4",
@@ -27,7 +28,6 @@
},
"devDependencies": {
"concurrently": "^9.1.2",
- "js-yaml": "^4.1.0",
"prettier": "^3.8.1"
},
"engines": {
@@ -215,7 +215,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
"license": "Python-2.0"
},
"node_modules/array-flatten": {
@@ -1099,7 +1098,6 @@
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
"integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
- "dev": true,
"funding": [
{
"type": "github",
diff --git a/package.json b/package.json
index e0efb296..b679d355 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "agenthub",
- "version": "2.8.9",
+ "version": "2.8.10",
"bin": {
"agenthub": "bin/agenthub.js"
},
@@ -75,6 +75,7 @@
"adm-zip": "^0.6.1",
"cors": "^2.8.5",
"express": "^4.21.2",
+ "js-yaml": "^4.1.0",
"multer": "^2.0.0",
"swagger-ui-express": "^5.0.1",
"systray2": "^2.1.4",
@@ -88,7 +89,6 @@
},
"devDependencies": {
"concurrently": "^9.1.2",
- "js-yaml": "^4.1.0",
"prettier": "^3.8.1"
},
"engines": {
diff --git a/server/__tests__/runtime-deps.test.js b/server/__tests__/runtime-deps.test.js
new file mode 100644
index 00000000..2b2c2024
--- /dev/null
+++ b/server/__tests__/runtime-deps.test.js
@@ -0,0 +1,68 @@
+/**
+ * @file runtime-deps.test.js
+ * @description Every package the server code requires is a runtime dependency.
+ * A plugin install runs `npm install --omit=dev`, so a package listed only in
+ * devDependencies — or one that arrived only as some other package's
+ * dependency — is missing there. v2.8.9 crashed at start on every plugin
+ * install: `js-yaml` (server/lib/lane-detect.js) was a devDependency that
+ * `redoc` used to pull in, and dropping `redoc` took it away. Machines with
+ * Debian's global /usr/share/nodejs never saw it, so this checks the manifest
+ * instead of trusting a local `require` to fail.
+ * @author Nguyễn Ngọc Trí Vĩ
+ */
+
+const { describe, it } = require("node:test");
+const assert = require("node:assert/strict");
+const fs = require("node:fs");
+const path = require("node:path");
+const { builtinModules } = require("node:module");
+
+const ROOT = path.join(__dirname, "..", "..");
+const pkg = require(path.join(ROOT, "package.json"));
+
+// Required only behind a runtime check, never on a plain Node server.
+const GUARDED = new Set(["electron"]);
+
+function sourceFiles(dir) {
+ const out = [];
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
+ const p = path.join(dir, e.name);
+ if (e.isDirectory()) {
+ if (!["node_modules", "__tests__", "vendor"].includes(e.name)) out.push(...sourceFiles(p));
+ } else if (/\.(c?js|mjs)$/.test(e.name)) out.push(p);
+ }
+ return out;
+}
+
+/** Bare package names `file` requires, comments left out. */
+function requiredPackages(file) {
+ const code = fs
+ .readFileSync(file, "utf8")
+ .replace(/\/\*[\s\S]*?\*\//g, "")
+ .replace(/(^|[^:"'`\\])\/\/.*$/gm, "$1");
+ const names = new Set();
+ for (const m of code.matchAll(/\brequire\(\s*["']([^"'./][^"']*)["']\s*\)/g)) {
+ const spec = m[1];
+ if (spec.startsWith("node:")) continue;
+ const name = spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
+ if (!builtinModules.includes(name)) names.add(name);
+ }
+ return names;
+}
+
+describe("runtime dependencies", () => {
+ it("every package server/ requires is in dependencies or optionalDependencies", () => {
+ const runtime = new Set([
+ ...Object.keys(pkg.dependencies || {}),
+ ...Object.keys(pkg.optionalDependencies || {}),
+ ]);
+ const missing = [];
+ for (const file of sourceFiles(path.join(ROOT, "server"))) {
+ for (const name of requiredPackages(file)) {
+ if (!runtime.has(name) && !GUARDED.has(name))
+ missing.push(`${path.relative(ROOT, file)}: ${name}`);
+ }
+ }
+ assert.deepEqual(missing, []);
+ });
+});