fix: v2.5.2 — plugin bootstrap exports before it runs (v2.5.1 update crash)

v2.5.1's update died at "bootstrap" on every platform: boot.npmCommand is
not a function. Run as the entry, plugin-bootstrap.js calls bootstrap() →
plugin-open.js, which requires plugin-bootstrap back and got the module.exports
of that moment — {} — because it was assigned at the bottom, below the entry
block. plugin-open.js already exported first for the same reason.

- module.exports moved above the entry block in plugin-bootstrap.js
- A test checks both entry scripts assign module.exports before their entry
  block (it fails on v2.5.1's file); requiring the module, as the other tests
  do, never takes the entry path
This commit is contained in:
2026-10-06 19:17:52 +07:00
parent be75572977
commit f8acd5fc29
7 changed files with 53 additions and 32 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -7233,7 +7233,7 @@ exports[`screen snapshots > Settings — about 1`] = `
<p <p
class="text-[10px] text-status-warning/90 mt-1" class="text-[10px] text-status-warning/90 mt-1"
> >
UI build v2.5.1 (rebuild client to match) UI build v2.5.2 (rebuild client to match)
</p> </p>
</div> </div>
<div <div
+1 -1
View File
@@ -3,7 +3,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: Agent Dashboard for Claude Code API title: Agent Dashboard for Claude Code API
version: 2.5.1 version: 2.5.2
description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence. description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence.
contact: contact:
name: Nguyễn Ngọc Trí Vĩ name: Nguyễn Ngọc Trí Vĩ
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "agenthub", "name": "agenthub",
"version": "2.5.1", "version": "2.5.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "agenthub", "name": "agenthub",
"version": "2.5.1", "version": "2.5.2",
"hasInstallScript": true, "hasInstallScript": true,
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "agenthub", "name": "agenthub",
"version": "2.5.1", "version": "2.5.2",
"bin": { "bin": {
"agenthub": "bin/agenthub.js" "agenthub": "bin/agenthub.js"
}, },
+30 -26
View File
@@ -853,32 +853,10 @@ function main() {
child.unref(); child.unref();
} }
if (require.main === module) { // Exported before the entry block below, not after it: `bootstrap()` requires
const force = process.argv.includes("--force"); // plugin-open.js, which requires this module back (`npmCommand`, `runtimeDir`),
if (force || process.argv.includes("--worker")) { // and when this file is the entry that require resolves to whatever is on
// `--force` (from /agenthub-update) runs in the foreground so the user sees the // module.exports at that moment — an empty object if it is assigned last.
// npm output and the result.
const result = bootstrap(runtimeDir(), { force });
if (force) {
console.log(
result === "ok"
? "AgentHub runtime refreshed."
: result === "superseded"
? "A newer AgentHub plugin build is already installed — this one did nothing on purpose.\n" +
"Restart Claude Code so it loads the new build, then try again."
: `bootstrap: ${result}`
);
if (result !== "ok") process.exitCode = 1;
}
} else {
try {
main();
} catch {
// A bootstrap failure must never break a Claude Code session.
}
}
}
module.exports = { module.exports = {
MIN_NODE, MIN_NODE,
LOCK_STALE_MS, LOCK_STALE_MS,
@@ -911,3 +889,29 @@ module.exports = {
startDashboard, startDashboard,
bootstrap, bootstrap,
}; };
if (require.main === module) {
const force = process.argv.includes("--force");
if (force || process.argv.includes("--worker")) {
// `--force` (from /agenthub-update) runs in the foreground so the user sees the
// npm output and the result.
const result = bootstrap(runtimeDir(), { force });
if (force) {
console.log(
result === "ok"
? "AgentHub runtime refreshed."
: result === "superseded"
? "A newer AgentHub plugin build is already installed — this one did nothing on purpose.\n" +
"Restart Claude Code so it loads the new build, then try again."
: `bootstrap: ${result}`
);
if (result !== "ok") process.exitCode = 1;
}
} else {
try {
main();
} catch {
// A bootstrap failure must never break a Claude Code session.
}
}
}
+17
View File
@@ -693,3 +693,20 @@ describe("npmCommand", () => {
assert.deepEqual(npm.args, ["-v"]); assert.deepEqual(npm.args, ["-v"]);
}); });
}); });
describe("entry scripts export before they run", () => {
// plugin-bootstrap.js run as the entry calls bootstrap() → plugin-open.js,
// which requires plugin-bootstrap back and gets whatever module.exports holds
// at that moment. Assigned after the entry block it was still {}, and v2.5.1's
// update died with "boot.npmCommand is not a function". Requiring the module
// (as every other test here does) never takes that path, hence this check.
for (const file of ["plugin-bootstrap.js", "plugin-open.js"]) {
it(file, () => {
const src = fs.readFileSync(path.join(__dirname, "..", "..", "scripts", file), "utf8");
const exportsAt = src.indexOf("\nmodule.exports = {");
const entryAt = src.indexOf("\nif (require.main === module)");
assert.ok(exportsAt > 0 && entryAt > 0, "both markers present");
assert.ok(exportsAt < entryAt, "module.exports must be assigned before the entry block");
});
}
});