fix(settings): quote numeric-shaped YAML keys so hash-keyed settings survive reload

The emitter wrote block-map keys raw while only values went through
quoting. A workspace-hash key segment that happens to be all digits
with a leading zero (or digits-e-digits) reloaded as an int/float —
leading zero dropped, or collapsed to Infinity — orphaning the stored
value. Hit ~1 in 1200 repos, deterministically and permanently: the
PATH preset (D-106) and the pre-existing Claude account binding for
such a repo vanished on every restart with no diagnostics. Same defect
class as the T-376 value-corruption fixes in this file; found by the
T-511 review pass, confirmed against the live store.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 18:17:15 +02:00
co-authored by Claude Fable 5
parent 880f642261
commit 49c82556a2
3 changed files with 26 additions and 1 deletions
+14
View File
@@ -37,6 +37,20 @@ void main() {
loaded.dispose();
});
test('numeric-shaped key segments round-trip unmangled (workspace-hash keys)', () async {
// FNV workspace-hash suffixes (app.env.pathPrepend.<hash>, the account
// bindings) can be number-shaped; an unquoted YAML key would reload as
// an int (leading zero dropped) or a float ('1e…' → Infinity) and
// silently orphan the stored value.
await store.set<List<String>>('app.env.pathPrepend.0123456789012345', const ['/opt/go/bin']);
await store.set<String>('app.claude.account.1e23456789012345', 'work');
final loaded = SettingsStore(appDir: tmp);
await loaded.load();
expect(loaded.get<List<dynamic>>('app.env.pathPrepend.0123456789012345'), ['/opt/go/bin']);
expect(loaded.get<String>('app.claude.account.1e23456789012345'), 'work');
loaded.dispose();
});
test('app.* scope supports bool + int + list', () async {
await store.set<bool>('app.extensions.git.enabled', false);
await store.set<int>('app.layout.width', 240);