From a06b12ff41a12142786e0765fa5e653872299ce4 Mon Sep 17 00:00:00 2001 From: Slava Zavadsky Date: Sat, 8 Aug 2026 10:10:29 -0400 Subject: [PATCH 1/2] test(coding-agent): hermeticize agent dir in claude-plugins discovery tests listClaudePluginRoots tests point HOME at a temp dir but left the agent dir at the real profile (~/.omp/agent). User-scope discovery (native MCP config via getAgentDir()/mcp.json) then read the machine's real mcp.json, leaking its servers into loadCapability results and failing 'deduplicates a plugin alias of a directly configured MCP connection' whenever the real agent dir contains an mcp.json. Point the agent dir at a temp dir in beforeEach and restore the previous value (or unset PI_CODING_AGENT_DIR) in afterEach, matching the established pattern in mcp-profile/autolearn-discovery tests. --- .../test/discovery/claude-plugins.test.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/test/discovery/claude-plugins.test.ts b/packages/coding-agent/test/discovery/claude-plugins.test.ts index 945e89096..51f94b1bf 100644 --- a/packages/coding-agent/test/discovery/claude-plugins.test.ts +++ b/packages/coding-agent/test/discovery/claude-plugins.test.ts @@ -11,7 +11,7 @@ import { } from "@oh-my-pi/pi-coding-agent/discovery/helpers"; import { loadSlashCommands } from "@oh-my-pi/pi-coding-agent/extensibility/slash-commands"; import { discoverAgents } from "@oh-my-pi/pi-coding-agent/task/discovery"; -import { removeWithRetries } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; import "@oh-my-pi/pi-coding-agent/discovery/claude-plugins"; import { type MCPServer, mcpCapability } from "@oh-my-pi/pi-coding-agent/capability/mcp"; import type { Skill } from "@oh-my-pi/pi-coding-agent/capability/skill"; @@ -62,27 +62,41 @@ describe("parseClaudePluginsRegistry", () => { describe("listClaudePluginRoots", () => { let tempDir: string; + let testAgentDir: string; let originalHome: string | undefined; + const originalAgentDirEnv = process.env.PI_CODING_AGENT_DIR; + const fallbackAgentDir = path.join(getConfigRootDir(), "agent"); beforeEach(async () => { clearClaudePluginRootsCache(); clearFsCache(); originalHome = process.env.HOME; tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "claude-plugins-test-")); + testAgentDir = await fs.mkdtemp(path.join(os.tmpdir(), "claude-plugins-test-agent-")); process.env.HOME = tempDir; vi.spyOn(os, "homedir").mockReturnValue(tempDir); + // Point the agent dir at a temp dir so user-scope discovery (native MCP + // config, skills, etc.) cannot read the real ~/.omp/agent profile. + setAgentDir(testAgentDir); }); afterEach(async () => { clearClaudePluginRootsCache(); clearFsCache(); vi.restoreAllMocks(); + if (originalAgentDirEnv) { + setAgentDir(originalAgentDirEnv); + } else { + setAgentDir(fallbackAgentDir); + delete process.env.PI_CODING_AGENT_DIR; + } if (originalHome === undefined) { delete process.env.HOME; } else { process.env.HOME = originalHome; } await removeWithRetries(tempDir); + await removeWithRetries(testAgentDir); }); test("returns empty roots when no registry file exists", async () => { From 8c4fde356442ba0105b8ce964b5fa5d44b297044 Mon Sep 17 00:00:00 2001 From: Slava Zavadsky Date: Sat, 8 Aug 2026 10:18:21 -0400 Subject: [PATCH 2/2] test(coding-agent): restore profile env after agent dir isolation setAgentDir() clears OMP_PROFILE/PI_PROFILE and snapshots the agent dir, so restoring only PI_CODING_AGENT_DIR/HOME leaks the default profile to later tests when the suite is started under a caller-selected profile. Capture and restore all three env vars plus HOME, then rebuild the dir resolver via __resetDirsFromEnvForTests() (disabled-extensions pattern). Addresses review comment on PR #8018. --- .../test/discovery/claude-plugins.test.ts | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/coding-agent/test/discovery/claude-plugins.test.ts b/packages/coding-agent/test/discovery/claude-plugins.test.ts index 51f94b1bf..cd5108efb 100644 --- a/packages/coding-agent/test/discovery/claude-plugins.test.ts +++ b/packages/coding-agent/test/discovery/claude-plugins.test.ts @@ -11,7 +11,7 @@ import { } from "@oh-my-pi/pi-coding-agent/discovery/helpers"; import { loadSlashCommands } from "@oh-my-pi/pi-coding-agent/extensibility/slash-commands"; import { discoverAgents } from "@oh-my-pi/pi-coding-agent/task/discovery"; -import { getConfigRootDir, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; +import { __resetDirsFromEnvForTests, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; import "@oh-my-pi/pi-coding-agent/discovery/claude-plugins"; import { type MCPServer, mcpCapability } from "@oh-my-pi/pi-coding-agent/capability/mcp"; import type { Skill } from "@oh-my-pi/pi-coding-agent/capability/skill"; @@ -60,17 +60,31 @@ describe("parseClaudePluginsRegistry", () => { }); }); +function restoreEnvValue(key: string, value: string | undefined): void { + if (value === undefined) { + delete process.env[key]; + delete Bun.env[key]; + return; + } + process.env[key] = value; + Bun.env[key] = value; +} + describe("listClaudePluginRoots", () => { let tempDir: string; let testAgentDir: string; let originalHome: string | undefined; - const originalAgentDirEnv = process.env.PI_CODING_AGENT_DIR; - const fallbackAgentDir = path.join(getConfigRootDir(), "agent"); + let originalAgentDirEnv: string | undefined; + let originalOmpProfileEnv: string | undefined; + let originalPiProfileEnv: string | undefined; beforeEach(async () => { clearClaudePluginRootsCache(); clearFsCache(); originalHome = process.env.HOME; + originalAgentDirEnv = process.env.PI_CODING_AGENT_DIR; + originalOmpProfileEnv = process.env.OMP_PROFILE; + originalPiProfileEnv = process.env.PI_PROFILE; tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "claude-plugins-test-")); testAgentDir = await fs.mkdtemp(path.join(os.tmpdir(), "claude-plugins-test-agent-")); process.env.HOME = tempDir; @@ -84,17 +98,13 @@ describe("listClaudePluginRoots", () => { clearClaudePluginRootsCache(); clearFsCache(); vi.restoreAllMocks(); - if (originalAgentDirEnv) { - setAgentDir(originalAgentDirEnv); - } else { - setAgentDir(fallbackAgentDir); - delete process.env.PI_CODING_AGENT_DIR; - } - if (originalHome === undefined) { - delete process.env.HOME; - } else { - process.env.HOME = originalHome; - } + // setAgentDir() clears the profile env vars and snapshots the agent dir, + // so restore every env var it can touch before rebuilding the resolver. + restoreEnvValue("HOME", originalHome); + restoreEnvValue("OMP_PROFILE", originalOmpProfileEnv); + restoreEnvValue("PI_PROFILE", originalPiProfileEnv); + restoreEnvValue("PI_CODING_AGENT_DIR", originalAgentDirEnv); + __resetDirsFromEnvForTests(); await removeWithRetries(tempDir); await removeWithRetries(testAgentDir); });