fix(cli-tools): tolerate JSONC configs in CLI tool settings routes
readConfig/readSettings/readJson across all CLI tool settings routes used JSON.parse() but only caught ENOENT, re-throwing SyntaxError on any corrupted or JSONC-formatted config file. The API would return 500, which the UI misinterpreted as "tool not installed." This patch: - strips trailing commas before parsing (handles JSONC from opencode, etc.) - returns null on any parse error instead of re-throwing, so the UI shows "installed but not configured" (accurate) instead of "not installed" - applies the same fix to all 8 affected routes: opencode, openclaw, kilo, droid, cowork, copilot, claude, and cline Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user