From d25a7d6fb78a04df938f82d49516c7ed550e4b79 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Tue, 7 Apr 2026 10:09:58 -0500 Subject: [PATCH 1/2] fix(gsd): suppress model change notification in auto-mode unless verbose The Model [phase] [tier] notification fired on every unit dispatch during auto-mode, cluttering the notification widget. The dashboard header already displays the active model, making this redundant. Gate behind verbose flag consistent with all other model routing notifications in the same function. --- src/resources/extensions/gsd/auto-model-selection.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/resources/extensions/gsd/auto-model-selection.ts b/src/resources/extensions/gsd/auto-model-selection.ts index ad26edca8..e1ef81e3a 100644 --- a/src/resources/extensions/gsd/auto-model-selection.ts +++ b/src/resources/extensions/gsd/auto-model-selection.ts @@ -246,11 +246,13 @@ export async function selectAndApplyModel( const ok = await pi.setModel(model, { persist: false }); if (ok) { appliedModel = model; - const fallbackNote = modelId === effectiveModelConfig.primary - ? "" - : ` (fallback from ${effectiveModelConfig.primary})`; - const phase = unitPhaseLabel(unitType); - ctx.ui.notify(`Model [${phase}]${routingTierLabel}: ${model.provider}/${model.id}${fallbackNote}`, "info"); + if (verbose) { + const fallbackNote = modelId === effectiveModelConfig.primary + ? "" + : ` (fallback from ${effectiveModelConfig.primary})`; + const phase = unitPhaseLabel(unitType); + ctx.ui.notify(`Model [${phase}]${routingTierLabel}: ${model.provider}/${model.id}${fallbackNote}`, "info"); + } break; } else { const nextModel = modelsToTry[modelsToTry.indexOf(modelId) + 1]; From 230939e5585b16fe3227218dc7132032eee3a896 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Tue, 7 Apr 2026 10:48:41 -0500 Subject: [PATCH 2/2] test(gsd): add regression test for verbose-gated model change notification Verifies that the Model [phase] [tier] notification in selectAndApplyModel is gated behind the verbose flag to prevent auto-mode notification noise. --- .../gsd/tests/auto-model-selection.test.ts | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/resources/extensions/gsd/tests/auto-model-selection.test.ts b/src/resources/extensions/gsd/tests/auto-model-selection.test.ts index 4ea3245a3..9cec5afac 100644 --- a/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +++ b/src/resources/extensions/gsd/tests/auto-model-selection.test.ts @@ -1,8 +1,11 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; import { tmpdir } from "node:os"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); import { resolvePreferredModelConfig, resolveModelId } from "../auto-model-selection.js"; @@ -196,6 +199,34 @@ test("resolveModelId: bare ID with claude-code as only provider still resolves", assert.equal(result.provider, "claude-code"); }); +// ─── selectAndApplyModel verbose-gating tests ────────────────────────── + +test("model change notify in selectAndApplyModel is gated behind verbose flag", () => { + // The Model [phase] [tier] notification should only fire when verbose=true. + // The dashboard header already shows the active model, so the notification + // is redundant noise during auto-mode (#3719). + const gsdDir = join(__dirname, ".."); + const src = readFileSync(join(gsdDir, "auto-model-selection.ts"), "utf-8"); + + // Find the block where setModel succeeds (appliedModel = model) and + // verify notify is inside an `if (verbose)` guard. + const setModelBlock = src.match( + /const ok = await pi\.setModel\(model[\s\S]*?appliedModel = model;([\s\S]*?)break;/, + ); + assert.ok(setModelBlock, "should find the setModel success block"); + + const blockBody = setModelBlock![1]; + // The notify call must be inside an if (verbose) block + assert.ok( + blockBody.includes("if (verbose)"), + "Model change ctx.ui.notify must be gated behind if (verbose) to avoid auto-mode notification noise", + ); + assert.ok( + blockBody.includes("ctx.ui.notify"), + "notify call should still exist (just verbose-gated)", + ); +}); + test("resolveModelId: anthropic wins over claude-code regardless of list order", () => { const availableModels = [ { id: "claude-sonnet-4-6", provider: "claude-code" },