fix: don't crash uninstall on malformed settings.json (#481)

If settings.json contained invalid JSON, JSON.parse threw a SyntaxError, which
has no .code, so the catch rethrew it and crashed the script — after the mode
flag and config file were already removed, leaving cleanup half-done. Handle
SyntaxError explicitly: warn that the statusLine entry couldn't be removed and
leave the file untouched, since invalid JSON can't be safely edited. Adds a
regression test that a malformed settings.json exits 0, warns, and is left
byte-for-byte intact.

Closes #434

Co-authored-by: isaukywhite <50426537+isaukywhite@users.noreply.github.com>
This commit is contained in:
DietrichGebert 2026-07-02 01:30:30 +02:00 committed by GitHub
parent b8f20b8e7c
commit 40e50d9e03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 30 additions and 1 deletions

View File

@ -48,5 +48,12 @@ try {
}
}
} catch (e) {
if (e.code !== 'ENOENT') throw e;
if (e.code === 'ENOENT') {
// no settings.json — nothing to clean
} else if (e instanceof SyntaxError) {
// ponytail: malformed settings.json — can't safely edit it; leave intact, warn
console.warn(`settings.json is malformed — could not remove the ponytail statusLine entry. Remove it manually from: ${settingsPath} (${e.message})`);
} else {
throw e;
}
}

View File

@ -84,6 +84,28 @@ assert.equal(
'a combined statusLine must be left untouched, not partially destroyed',
);
// #434: a malformed settings.json must not crash the script mid-cleanup. It
// can't be safely edited, so uninstall warns and leaves the file byte-for-byte
// intact instead of throwing a SyntaxError after other state was already removed.
const malformedSettings = '{ "statusLine": { "command": "ponytail-statusline.sh", broken';
fs.writeFileSync(settingsPath, malformedSettings);
result = runUninstall(env);
assert.equal(
result.status,
0,
`expected exit 0 on malformed settings.json, got:\n${result.stdout}${result.stderr}`,
);
assert.ok(
/malformed/i.test(result.stdout + result.stderr),
'must warn that the statusLine entry could not be removed',
);
assert.equal(
fs.readFileSync(settingsPath, 'utf8'),
malformedSettings,
'malformed settings.json must be left unchanged',
);
// Running on an already-clean machine must not throw.
result = runUninstall({ HOME: path.join(temp, 'home-empty'), USERPROFILE: path.join(temp, 'home-empty') });
assert.equal(result.status, 0, result.stderr);