diff --git a/src/app/api/cli-tools/claude-settings/route.js b/src/app/api/cli-tools/claude-settings/route.js index 121879dad7..bb5fa06fc4 100644 --- a/src/app/api/cli-tools/claude-settings/route.js +++ b/src/app/api/cli-tools/claude-settings/route.js @@ -41,12 +41,12 @@ const readSettings = async () => { try { const settingsPath = getClaudeSettingsPath(); const content = await fs.readFile(settingsPath, "utf-8"); - return JSON.parse(content); + // Tolerate JSONC (trailing commas) and treat unparseable files as "no config" + // rather than throwing a 500 that the UI misreads as "tool not installed". + const stripped = content.replace(/,(\s*[}\]])/g, "$1"); + return JSON.parse(stripped); } catch (error) { - if (error.code === "ENOENT") { - return null; - } - throw error; + return null; } }; diff --git a/src/app/api/cli-tools/cline-settings/route.js b/src/app/api/cli-tools/cline-settings/route.js index cc72397932..ecbccbd250 100644 --- a/src/app/api/cli-tools/cline-settings/route.js +++ b/src/app/api/cli-tools/cline-settings/route.js @@ -35,10 +35,12 @@ const checkInstalled = async () => { const readJson = async (filePath) => { try { const content = await fs.readFile(filePath, "utf-8"); - return JSON.parse(content); + // Tolerate JSONC (trailing commas) and treat unparseable files as "no config" + // rather than throwing a 500 that the UI misreads as "tool not installed". + const stripped = content.replace(/,(\s*[}\]])/g, "$1"); + return JSON.parse(stripped); } catch (error) { - if (error.code === "ENOENT") return null; - throw error; + return null; } }; diff --git a/src/app/api/cli-tools/copilot-settings/route.js b/src/app/api/cli-tools/copilot-settings/route.js index 449c6e2c0a..3c0bd6699a 100644 --- a/src/app/api/cli-tools/copilot-settings/route.js +++ b/src/app/api/cli-tools/copilot-settings/route.js @@ -21,10 +21,12 @@ const getConfigPath = () => { const readConfig = async () => { try { const content = await fs.readFile(getConfigPath(), "utf-8"); - return JSON.parse(content); + // Tolerate JSONC (trailing commas) and treat unparseable files as "no config" + // rather than throwing a 500 that the UI misreads as "tool not installed". + const stripped = content.replace(/,(\s*[}\]])/g, "$1"); + return JSON.parse(stripped); } catch (error) { - if (error.code === "ENOENT") return null; - throw error; + return null; } }; diff --git a/src/app/api/cli-tools/cowork-settings/route.js b/src/app/api/cli-tools/cowork-settings/route.js index e09ec28306..d30b667e33 100644 --- a/src/app/api/cli-tools/cowork-settings/route.js +++ b/src/app/api/cli-tools/cowork-settings/route.js @@ -116,10 +116,14 @@ const get1pRoot = () => { const get1pConfigPath = () => path.join(get1pRoot(), "claude_desktop_config.json"); const read1pConfig = async () => { - try { return JSON.parse(await fs.readFile(get1pConfigPath(), "utf-8")) || {}; } - catch (error) { - if (error.code === "ENOENT") return {}; - throw error; + try { + const content = await fs.readFile(get1pConfigPath(), "utf-8"); + // Tolerate JSONC (trailing commas) and treat unparseable files as empty config + // rather than throwing a 500 that the UI misreads as "tool not installed". + const stripped = content.replace(/,(\s*[}\]])/g, "$1"); + return JSON.parse(stripped) || {}; + } catch (error) { + return {}; } }; @@ -193,10 +197,14 @@ const checkInstalled = async () => { }; const readJson = async (filePath) => { - try { return JSON.parse(await fs.readFile(filePath, "utf-8")); } - catch (error) { - if (error.code === "ENOENT") return null; - throw error; + try { + const content = await fs.readFile(filePath, "utf-8"); + // Tolerate JSONC (trailing commas) and treat unparseable files as "no config" + // rather than throwing a 500 that the UI misreads as "tool not installed". + const stripped = content.replace(/,(\s*[}\]])/g, "$1"); + return JSON.parse(stripped); + } catch (error) { + return null; } }; diff --git a/src/app/api/cli-tools/droid-settings/route.js b/src/app/api/cli-tools/droid-settings/route.js index 34f5326258..a4162578d4 100644 --- a/src/app/api/cli-tools/droid-settings/route.js +++ b/src/app/api/cli-tools/droid-settings/route.js @@ -37,10 +37,12 @@ const readSettings = async () => { try { const settingsPath = getDroidSettingsPath(); const content = await fs.readFile(settingsPath, "utf-8"); - return JSON.parse(content); + // Tolerate JSONC (trailing commas) and treat unparseable files as "no config" + // rather than throwing a 500 that the UI misreads as "tool not installed". + const stripped = content.replace(/,(\s*[}\]])/g, "$1"); + return JSON.parse(stripped); } catch (error) { - if (error.code === "ENOENT") return null; - throw error; + return null; } }; diff --git a/src/app/api/cli-tools/kilo-settings/route.js b/src/app/api/cli-tools/kilo-settings/route.js index 6802adc8db..9c5993f2c2 100644 --- a/src/app/api/cli-tools/kilo-settings/route.js +++ b/src/app/api/cli-tools/kilo-settings/route.js @@ -35,10 +35,12 @@ const checkInstalled = async () => { const readJson = async (filePath) => { try { const content = await fs.readFile(filePath, "utf-8"); - return JSON.parse(content); + // Tolerate JSONC (trailing commas) and treat unparseable files as "no config" + // rather than throwing a 500 that the UI misreads as "tool not installed". + const stripped = content.replace(/,(\s*[}\]])/g, "$1"); + return JSON.parse(stripped); } catch (error) { - if (error.code === "ENOENT") return null; - throw error; + return null; } }; diff --git a/src/app/api/cli-tools/openclaw-settings/route.js b/src/app/api/cli-tools/openclaw-settings/route.js index 85af6a92c2..2047a256c3 100644 --- a/src/app/api/cli-tools/openclaw-settings/route.js +++ b/src/app/api/cli-tools/openclaw-settings/route.js @@ -47,10 +47,12 @@ const readSettings = async () => { try { const settingsPath = getOpenClawSettingsPath(); const content = await fs.readFile(settingsPath, "utf-8"); - return JSON.parse(content); + // Tolerate JSONC (trailing commas) and treat unparseable files as "no config" + // rather than throwing a 500 that the UI misreads as "tool not installed". + const stripped = content.replace(/,(\s*[}\]])/g, "$1"); + return JSON.parse(stripped); } catch (error) { - if (error.code === "ENOENT") return null; - throw error; + return null; } }; diff --git a/src/app/api/cli-tools/opencode-settings/route.js b/src/app/api/cli-tools/opencode-settings/route.js index f4429cf5e9..03819c6619 100644 --- a/src/app/api/cli-tools/opencode-settings/route.js +++ b/src/app/api/cli-tools/opencode-settings/route.js @@ -35,10 +35,16 @@ const checkOpenCodeInstalled = async () => { const readConfig = async () => { try { const content = await fs.readFile(getConfigPath(), "utf-8"); - return JSON.parse(content); + // opencode config files may use JSONC format (trailing commas, comments). + // Strip trailing commas before parsing to avoid SyntaxError on valid JSONC. + const stripped = content.replace(/,(\s*[}\]])/g, "$1"); + return JSON.parse(stripped); } catch (error) { if (error.code === "ENOENT") return null; - throw error; + // If the config file exists but is unparseable (corrupted, exotic JSONC), + // treat it as "no config" rather than throwing a 500 that the UI + // misinterprets as "opencode not installed". + return null; } };