From 114823ceefe3705c212a4ca6861a7ef468e81be2 Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Wed, 9 Sep 2026 10:09:24 +0700 Subject: [PATCH] fix(cc-config): stop pinning CLAUDE_CONFIG_DIR in the bootstrap's plugin installer too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit skill-plugins.js carried its own copy of the unconditional pin, with the same comment claiming it keeps writes where cc-discovery reads. Same rule as runClaude now: pin only when the operator set CLAUDE_HOME. Also corrects three comments that described the old behaviour — the confidently-wrong pair at these two call sites is what let two readers believe reads and writes were aligned when they were not. --- server/lib/cc-plugin-cli.js | 14 +++++++------- server/lib/skill-plugins.js | 12 ++++++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/server/lib/cc-plugin-cli.js b/server/lib/cc-plugin-cli.js index ac7aebee..b9f78045 100644 --- a/server/lib/cc-plugin-cli.js +++ b/server/lib/cc-plugin-cli.js @@ -17,8 +17,10 @@ * per argument kind. * - Mutating calls are serialized in-process; two dashboard clicks must not * interleave writes to the same manifest. - * - `CLAUDE_CONFIG_DIR` is pinned to the same CLAUDE_HOME the dashboard - * reads, so the CLI writes where `cc-discovery` looks. + * - `CLAUDE_CONFIG_DIR` is pinned ONLY when the operator set `CLAUDE_HOME`. + * Left unset it stays unset, because the CLI's own defaults already write + * where `cc-discovery` looks — pinning it unconditionally is what once + * made every MCP add/remove write a file nothing reads. * * @author Nguyễn Ngọc Trí Vĩ */ @@ -138,11 +140,9 @@ function runClaude(args, opts = {}) { // function's own comment). Pinning CLAUDE_CONFIG_DIR unconditionally to // getClaudeHome() made the CLI write `~/.claude/.claude.json` instead — // a file nothing reads — so every default-install add/remove was inert. - // When CLAUDE_HOME IS set, this still pins it, but note the residual: - // cc-discovery's read stays pinned to $HOME/.claude.json regardless (see - // getClaudeJsonPath's comment) while the CLI then writes - // /.claude.json — a pre-existing split this task does not - // attempt to fix, since no install in this repo runs with CLAUDE_HOME set. + // When CLAUDE_HOME IS set, this pins it and the CLI writes + // /.claude.json — which is what getClaudeJsonPath() reads in + // that branch, so both cases line up. Keep the two in step. const env = { ...process.env }; if (process.env.CLAUDE_HOME) env.CLAUDE_CONFIG_DIR = getClaudeHome(); const child = execFile( diff --git a/server/lib/skill-plugins.js b/server/lib/skill-plugins.js index cdd0be9e..3d8a66cc 100644 --- a/server/lib/skill-plugins.js +++ b/server/lib/skill-plugins.js @@ -119,20 +119,24 @@ function missingSkillPlugins() { * because every argv element below comes from {@link AUTO_INSTALLABLE} — a * constant in this file, never a request or a template. * - * Still an argv array, never a shell string, and `CLAUDE_CONFIG_DIR` is pinned - * to the same home `cc-discovery` reads so a write here is a write the - * dashboard can see. + * Still an argv array, never a shell string. `CLAUDE_CONFIG_DIR` is pinned + * only when the operator set `CLAUDE_HOME`, matching `cc-plugin-cli.js`'s + * `runClaude()`: left unset, the CLI's own defaults already write where + * `cc-discovery` reads, and pinning it unconditionally sent `.claude.json` + * to `~/.claude/.claude.json`, which nothing reads. * * @returns {{ok: boolean, output: string}} */ function runClaudePlugin(args, timeoutMs) { + const env = { ...process.env }; + if (process.env.CLAUDE_HOME) env.CLAUDE_CONFIG_DIR = getClaudeHome(); const result = spawnSync("claude", args, { timeout: timeoutMs, encoding: "utf8", maxBuffer: 1024 * 1024, // Non-interactive: a would-be prompt must fail fast, not hang out the timeout. stdio: ["ignore", "pipe", "pipe"], - env: { ...process.env, CLAUDE_CONFIG_DIR: getClaudeHome() }, + env, }); const output = `${result.stdout || ""}${result.stderr || ""}`.trim(); if (result.error) {