diff --git a/server/__tests__/skill-plugins.test.js b/server/__tests__/skill-plugins.test.js index 61ed9aea..4a7ea256 100644 --- a/server/__tests__/skill-plugins.test.js +++ b/server/__tests__/skill-plugins.test.js @@ -238,3 +238,52 @@ describe("autoInstallSkillPlugins", () => { assert.deepEqual(run.calls, []); }); }); + +describe("runClaudePlugin: the env it spawns with", () => { + // Every other test in this file injects `run`, so nothing exercises the real + // spawn. That gap is how a wrong CLAUDE_CONFIG_DIR pin lived here: the CLI + // writes `.claude.json` INSIDE that dir, while cc-discovery reads it beside + // ~/.claude by default, so pinning it unconditionally aimed writes at a file + // nothing reads. cc-plugin-cli.js's runClaude has the mirror of this test. + const PRINT_ENV = 'printf "%s\\n" "CLAUDE_CONFIG_DIR=${CLAUDE_CONFIG_DIR-}"'; + + function stubClaude(body) { + const bin = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-skillplug-bin-")); + const file = path.join(bin, "claude"); + fs.writeFileSync(file, `#!/usr/bin/env bash\n${body}\n`); + fs.chmodSync(file, 0o755); + const originalPath = process.env.PATH; + process.env.PATH = `${bin}${path.delimiter}${originalPath}`; + return () => { + process.env.PATH = originalPath; + fs.rmSync(bin, { recursive: true, force: true }); + }; + } + + it("leaves CLAUDE_CONFIG_DIR unset when CLAUDE_HOME is not set", () => { + const original = process.env.CLAUDE_HOME; + delete process.env.CLAUDE_HOME; + const restore = stubClaude(PRINT_ENV); + try { + const res = skillPlugins.runClaudePlugin(["plugin", "list"], 10_000); + assert.equal(res.output.trim(), "CLAUDE_CONFIG_DIR="); + } finally { + restore(); + if (original !== undefined) process.env.CLAUDE_HOME = original; + } + }); + + it("pins CLAUDE_CONFIG_DIR to CLAUDE_HOME when the operator set it", () => { + const original = process.env.CLAUDE_HOME; + process.env.CLAUDE_HOME = "/tmp/some-other-claude-home"; + const restore = stubClaude(PRINT_ENV); + try { + const res = skillPlugins.runClaudePlugin(["plugin", "list"], 10_000); + assert.equal(res.output.trim(), "CLAUDE_CONFIG_DIR=/tmp/some-other-claude-home"); + } finally { + restore(); + if (original === undefined) delete process.env.CLAUDE_HOME; + else process.env.CLAUDE_HOME = original; + } + }); +}); diff --git a/server/lib/skill-plugins.js b/server/lib/skill-plugins.js index 3d8a66cc..21bbc85a 100644 --- a/server/lib/skill-plugins.js +++ b/server/lib/skill-plugins.js @@ -220,4 +220,8 @@ module.exports = { requiredSkillPlugins, missingSkillPlugins, autoInstallSkillPlugins, + // Exported only so a test can pin the env it spawns with. Every other test + // injects `run` and so never reaches the real spawn — which is how an + // unconditional CLAUDE_CONFIG_DIR pin could be restored here unnoticed. + runClaudePlugin, };