From d21db9f398e9bdd5bfbd16477953867eaeb4d005 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 25 Mar 2026 00:06:37 -0400 Subject: [PATCH] fix(preferences): deduplicate unrecognized format warning on repeated loads (#2375) parsePreferencesMarkdown emitted a console.warn every time preferences were loaded with an unrecognized format, spamming stderr on each call to loadEffectiveGSDPreferences. Gate the warning behind a warn-once flag so it prints at most once per process. Fixes #2373 Co-authored-by: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/preferences.ts | 12 ++++++++- .../extensions/gsd/tests/preferences.test.ts | 27 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/preferences.ts b/src/resources/extensions/gsd/preferences.ts index 99c91e370..509ac7f61 100644 --- a/src/resources/extensions/gsd/preferences.ts +++ b/src/resources/extensions/gsd/preferences.ts @@ -196,6 +196,13 @@ function loadPreferencesFile(path: string, scope: "global" | "project"): LoadedG }; } +let _warnedUnrecognizedFormat = false; + +/** @internal Reset the warn-once flag — exported for testing only. */ +export function _resetParseWarningFlag(): void { + _warnedUnrecognizedFormat = false; +} + /** @internal Exported for testing only */ export function parsePreferencesMarkdown(content: string): GSDPreferences | null { // Use indexOf instead of [\s\S]*? regex to avoid backtracking (#468) @@ -214,7 +221,10 @@ export function parsePreferencesMarkdown(content: string): GSDPreferences | null return parseHeadingListFormat(content); } - console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping."); + if (!_warnedUnrecognizedFormat) { + _warnedUnrecognizedFormat = true; + console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping."); + } return null; } diff --git a/src/resources/extensions/gsd/tests/preferences.test.ts b/src/resources/extensions/gsd/tests/preferences.test.ts index 9dc9ed662..26ac7261d 100644 --- a/src/resources/extensions/gsd/tests/preferences.test.ts +++ b/src/resources/extensions/gsd/tests/preferences.test.ts @@ -15,6 +15,7 @@ import { applyModeDefaults, getIsolationMode, parsePreferencesMarkdown, + _resetParseWarningFlag, } from "../preferences.ts"; import type { GSDPreferences, GSDModelConfigV2, GSDPhaseModelConfig } from "../preferences.ts"; @@ -352,3 +353,29 @@ test("handles empty models config", () => { assert.notEqual(prefs, null); assert.equal(prefs!.models, undefined); }); + +// ── Warn-once for unrecognized format (#2373) ──────────────────────────────── + +test("unrecognized format warning is emitted at most once (#2373)", () => { + const warnings: string[] = []; + const origWarn = console.warn; + console.warn = (...args: unknown[]) => warnings.push(args.join(" ")); + try { + // Reset internal warned flag so the test starts clean + _resetParseWarningFlag(); + + const unrecognized = "This is just plain text with no frontmatter or headings."; + + // Call multiple times — simulates repeated preference loads + parsePreferencesMarkdown(unrecognized); + parsePreferencesMarkdown(unrecognized); + parsePreferencesMarkdown(unrecognized); + + const relevant = warnings.filter(w => w.includes("unrecognized format")); + assert.equal(relevant.length, 1, `expected exactly 1 warning, got ${relevant.length}: ${JSON.stringify(relevant)}`); + } finally { + console.warn = origWarn; + // Reset so other tests aren't affected by the flag state + _resetParseWarningFlag(); + } +});