fix: writeDefaultMode merges config instead of overwriting (#490) (#514)

writeDefaultMode was writing a fresh { defaultMode } object, silently
dropping any other fields in config.json. Now it reads the existing config,
updates only defaultMode, and writes it back preserving all other fields.

Fixes #490
This commit is contained in:
Aly Dhedhi 2026-07-06 20:44:56 -04:00 committed by GitHub
parent 3869218b13
commit 988428d510
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 27 additions and 2 deletions

View File

@ -102,7 +102,13 @@ function writeDefaultMode(mode) {
const configPath = getConfigPath();
fs.mkdirSync(path.dirname(configPath), { recursive: true });
fs.writeFileSync(configPath, JSON.stringify({ defaultMode: normalized }, null, 2), 'utf8');
let config = {};
try {
config = JSON.parse(fs.readFileSync(configPath, 'utf8').replace(/^\uFEFF/, ''));
if (!config || typeof config !== 'object' || Array.isArray(config)) config = {};
} catch (_) {}
config.defaultMode = normalized;
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
return normalized;
}

View File

@ -11,7 +11,7 @@ const root = path.join(__dirname, '..');
// isShellSafe gates the statusline setup snippet (issue #200): ordinary install
// paths pass, paths carrying shell metacharacters are rejected so they never get
// embedded in a shell command.
const { isShellSafe } = require('../hooks/ponytail-config');
const { isShellSafe, writeDefaultMode } = require('../hooks/ponytail-config');
assert.equal(isShellSafe('C:\\Users\\x\\.claude\\plugins\\ponytail\\hooks\\ponytail-statusline.ps1'), true);
assert.equal(isShellSafe('/home/u/.claude/plugins/ponytail/hooks/ponytail-statusline.sh'), true);
assert.equal(isShellSafe('/tmp/a"&calc.exe&"/x.sh'), false);
@ -209,4 +209,23 @@ assert.equal(output.systemMessage, 'PONYTAIL:FULL');
assert.equal(output.hookSpecificOutput.hookEventName, 'SubagentStart');
assert.match(output.hookSpecificOutput.additionalContext, /PONYTAIL MODE ACTIVE — level: full/);
// writeDefaultMode must merge into existing config, not overwrite it (#490).
const mergeHome = path.join(temp, 'merge-home');
const mergeConfigDir = path.join(mergeHome, '.config', 'ponytail');
fs.mkdirSync(mergeConfigDir, { recursive: true });
const mergeConfigPath = path.join(mergeConfigDir, 'config.json');
fs.writeFileSync(mergeConfigPath, JSON.stringify({ defaultMode: 'full', customSetting: 42 }, null, 2));
const prevXdg = process.env.XDG_CONFIG_HOME;
process.env.XDG_CONFIG_HOME = path.join(mergeHome, '.config');
try {
writeDefaultMode('ultra');
const merged = JSON.parse(fs.readFileSync(mergeConfigPath, 'utf8'));
assert.equal(merged.defaultMode, 'ultra', 'writeDefaultMode must update defaultMode');
assert.equal(merged.customSetting, 42, 'writeDefaultMode must preserve existing config fields');
} finally {
if (prevXdg === undefined) delete process.env.XDG_CONFIG_HOME;
else process.env.XDG_CONFIG_HOME = prevXdg;
}
console.log('hook compatibility checks passed');