* fix(#1657): recover malformed (non-object) ~/.gsd/defaults.json in finishInstall JSON.parse of defaults.json succeeds for valid-JSON-but-non-object values (null, [], 42, "str"), which then bypassed the parse catch: null threw a TypeError on property access (swallowed by the outer try/catch), and array/number/string had resolve_model_ids set on a non-object whose JSON.stringify round-trip kept the broken shape. The non-Claude finishInstall step now resets any non-object (null, non-object, or array) parse result to {} before reading/writing, so the file is repaired and resolve_model_ids defaults normally. Regression folded into the owning tests/bug-410-install-defaults-test-mode-guard.test.cjs (parameterized over null/[]/42/"str"). * chore(#1657): backfill changeset pr ref to 1661
This commit is contained in:
@@ -254,3 +254,40 @@ describe('Bug #1569: non-Claude finishInstall preserves explicit resolve_model_i
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Bug #1657 — finishInstall reads ~/.gsd/defaults.json with JSON.parse but did not
|
||||
// validate the result is a plain object. A valid-JSON-but-non-object value (null, [],
|
||||
// 42, "str") bypassed the catch and flowed through, leaving the malformed file on disk
|
||||
// unrecovered (and, for null, throwing a TypeError swallowed by the outer try/catch).
|
||||
// Folded into the owning install-defaults test (no new top-level bug-NNNN file).
|
||||
describe('Bug #1657: finishInstall recovers a malformed (non-object) defaults.json', () => {
|
||||
function seedDefaultsRaw(raw) {
|
||||
fs.mkdirSync(GSD_DIR, { recursive: true });
|
||||
fs.writeFileSync(DEFAULTS_PATH, raw, 'utf8');
|
||||
}
|
||||
function runAndRead(runtime) {
|
||||
const saved = process.env.GSD_TEST_MODE;
|
||||
delete process.env.GSD_TEST_MODE;
|
||||
const log = console.log; console.log = () => {};
|
||||
let threw = null;
|
||||
try {
|
||||
installModule.finishInstall(SETTINGS_PATH, {}, null, false, runtime, true, null);
|
||||
} catch (e) { threw = e.message; } finally { console.log = log; process.env.GSD_TEST_MODE = saved; }
|
||||
let after = null;
|
||||
try { after = JSON.parse(fs.readFileSync(DEFAULTS_PATH, 'utf8')); } catch (e) { after = 'UNPARSEABLE: ' + e.message; }
|
||||
return { threw, after };
|
||||
}
|
||||
|
||||
for (const [label, raw] of [['null', 'null'], ['array', '[]'], ['number', '42'], ['string', '"oops"']]) {
|
||||
test(`seed ${label} (${raw}) recovers to a valid object with resolve_model_ids:omit`, () => {
|
||||
seedDefaultsRaw(raw);
|
||||
const { threw, after } = runAndRead('codex');
|
||||
assert.equal(threw, null, `must not throw for seed ${label} (got: ${threw})`);
|
||||
assert.equal(
|
||||
after !== null && typeof after === 'object' && !Array.isArray(after) && after.resolve_model_ids === 'omit',
|
||||
true,
|
||||
`seed ${label} must recover to { resolve_model_ids: 'omit' }, got: ${JSON.stringify(after)}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user