mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-30 12:40:44 +00:00
fix: prevent statusline script from corrupting settings.json (#3091)
* fix: prevent statusline script from corrupting settings.json Some models generate shell commands with complex quoting (e.g. single-quote escaping like '\'') that break JSON syntax when written to settings.json, causing qwen-code to fail to start with a FatalConfigError. This adds four layers of defense: 1. **Agent prompt** (builtin-agents.ts): Require commands using jq/pipes/quotes to be saved as script files instead of inline in settings.json. Mark examples as script-only to prevent models from copying them inline. 2. **Write validation** (commentJson.ts): Validate JSON output before writing to disk in updateSettingsFilePreservingFormat. 3. **Startup recovery** (settings.ts): When settings.json has invalid JSON, try .orig backup first, then degrade gracefully to empty settings instead of crashing. Rename corrupted file to .corrupted for manual recovery. Show warning to user via migrationWarnings. 4. **Test update** (settings.test.ts): Update test to verify graceful degradation behavior instead of expecting FatalConfigError. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review comments on statusline JSON corruption fix 1. Backup recovery now surfaces warning via migrationWarnings (reviewer: P2 correctness) 2. Corrupted file uses timestamped suffix to avoid overwriting (reviewer: P2 robustness) 3. Remove misleading underscore prefix on used catch variable (reviewer: P2 code quality) 4. updateSettingsFilePreservingFormat returns boolean (reviewer: P2 correctness) 5. Add 3 new tests: backup recovery, both-corrupted, rename-failure (reviewer: P2 testing) 6. Consistent shebang lines in agent prompt examples (reviewer: P3 nit) 7. Improve catch block error message for backup recovery (reviewer: P2 correctness) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: warningMsg says "renamed" even when rename fails Move warningMsg construction after renameSync so the message accurately reflects the outcome: "renamed to X" on success, "fix manually" on failure. Add assertion to rename-failure test verifying the fallback message. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ec1787b846
commit
2ac099caaf
4 changed files with 263 additions and 47 deletions
|
|
@ -572,8 +572,62 @@ export function loadSettings(
|
|||
): { settings: Settings; rawJson?: string; migrationWarnings?: string[] } => {
|
||||
try {
|
||||
if (fs.existsSync(filePath)) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const rawSettings: unknown = JSON.parse(stripJsonComments(content));
|
||||
let content = fs.readFileSync(filePath, 'utf-8');
|
||||
let rawSettings: unknown;
|
||||
let recoveryWarning: string | undefined;
|
||||
|
||||
try {
|
||||
rawSettings = JSON.parse(stripJsonComments(content));
|
||||
} catch (parseError: unknown) {
|
||||
// JSON parse failed — try to recover from .orig backup
|
||||
const backupPath = `${filePath}.orig`;
|
||||
if (fs.existsSync(backupPath)) {
|
||||
debugLogger.warn(
|
||||
`Settings file ${filePath} has invalid JSON (${getErrorMessage(parseError)}). Attempting recovery from backup ${backupPath}.`,
|
||||
);
|
||||
try {
|
||||
const backupContent = fs.readFileSync(backupPath, 'utf-8');
|
||||
const backupSettings = JSON.parse(
|
||||
stripJsonComments(backupContent),
|
||||
);
|
||||
// Backup is valid — restore it
|
||||
fs.writeFileSync(filePath, backupContent, 'utf-8');
|
||||
content = backupContent;
|
||||
rawSettings = backupSettings;
|
||||
const recoveryMsg = `Settings file ${filePath} had invalid JSON and was recovered from backup ${backupPath}. Some recent settings changes may have been lost.`;
|
||||
debugLogger.warn(recoveryMsg);
|
||||
// Surface warning to user so they know settings were rolled back
|
||||
recoveryWarning = recoveryMsg;
|
||||
} catch (backupError) {
|
||||
// Could be invalid JSON, read error, or write-back failure
|
||||
debugLogger.warn(
|
||||
`Failed to recover from backup ${backupPath}: ${getErrorMessage(backupError)}. Falling back to empty settings.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// No valid backup available — rename the corrupted file so the app
|
||||
// can start with empty settings rather than crashing.
|
||||
if (!rawSettings) {
|
||||
const corruptedPath = `${filePath}.corrupted.${Date.now()}`;
|
||||
let warningMsg: string;
|
||||
try {
|
||||
fs.renameSync(filePath, corruptedPath);
|
||||
warningMsg = `Settings file ${filePath} has invalid JSON and was renamed to ${corruptedPath}. Your settings have been reset. To recover, fix the JSON in ${corruptedPath} and rename it back.`;
|
||||
} catch (renameError) {
|
||||
// If rename fails, still proceed with empty settings
|
||||
debugLogger.error(
|
||||
`Failed to rename corrupted settings file: ${getErrorMessage(renameError)}`,
|
||||
);
|
||||
warningMsg = `Settings file ${filePath} has invalid JSON. Your settings have been reset. Please fix the JSON in ${filePath} manually.`;
|
||||
}
|
||||
debugLogger.warn(warningMsg);
|
||||
return {
|
||||
settings: {},
|
||||
migrationWarnings: [warningMsg],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof rawSettings !== 'object' ||
|
||||
|
|
@ -636,10 +690,17 @@ export function loadSettings(
|
|||
persistSettingsObject('Error normalizing settings version on disk');
|
||||
}
|
||||
|
||||
// Prepend recovery warning if settings were restored from backup
|
||||
const allWarnings = [
|
||||
...(recoveryWarning ? [recoveryWarning] : []),
|
||||
...(migrationWarnings ?? []),
|
||||
];
|
||||
|
||||
return {
|
||||
settings: settingsObject as Settings,
|
||||
rawJson: content,
|
||||
migrationWarnings,
|
||||
migrationWarnings:
|
||||
allWarnings.length > 0 ? allWarnings : migrationWarnings,
|
||||
};
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue