From 131cb1bed21538c4b9e8c79753b26e976d4f598d Mon Sep 17 00:00:00 2001 From: Nils Reeh Date: Sat, 4 Apr 2026 01:51:18 +0200 Subject: [PATCH] fix(remote-questions): fire configured channels in interactive mode tryRemoteQuestions was gated behind if (!ctx.hasUI), so Telegram/Slack/ Discord were never contacted when GSD ran with a terminal UI. The test message sent during setup always worked (direct API call, no guard), which made the feature appear configured but non-functional in practice. Move the remote call before the hasUI guard so configured channels fire regardless of UI availability. When no remote channel is configured, tryRemoteQuestions returns null and the local UI is used as before. Adds a source-level regression test asserting that tryRemoteQuestions is called before the !ctx.hasUI branch. Closes #3480 Verified with AI. --- .../extensions/ask-user-questions.ts | 10 ++++++--- .../gsd/tests/remote-questions.test.ts | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/resources/extensions/ask-user-questions.ts b/src/resources/extensions/ask-user-questions.ts index c227c1ad4..af28c951f 100644 --- a/src/resources/extensions/ask-user-questions.ts +++ b/src/resources/extensions/ask-user-questions.ts @@ -135,10 +135,14 @@ export default function AskUserQuestions(pi: ExtensionAPI) { } } + // Try remote first if configured (works in both interactive and headless modes). + // tryRemoteQuestions returns null when no remote channel is configured, so + // this is a no-op when the user has not set up Slack/Discord/Telegram. + const { tryRemoteQuestions } = await import("./remote-questions/manager.js"); + const remoteResult = await tryRemoteQuestions(params.questions, signal); + if (remoteResult) return { ...remoteResult, details: remoteResult.details as unknown }; + if (!ctx.hasUI) { - const { tryRemoteQuestions } = await import("./remote-questions/manager.js"); - const remoteResult = await tryRemoteQuestions(params.questions, signal); - if (remoteResult) return { ...remoteResult, details: remoteResult.details as unknown }; return errorResult("Error: UI not available (non-interactive mode)", params.questions); } diff --git a/src/resources/extensions/gsd/tests/remote-questions.test.ts b/src/resources/extensions/gsd/tests/remote-questions.test.ts index 23432a2c0..4cd08b339 100644 --- a/src/resources/extensions/gsd/tests/remote-questions.test.ts +++ b/src/resources/extensions/gsd/tests/remote-questions.test.ts @@ -739,6 +739,27 @@ test("config source-level: hydration skips api_key entries with empty keys", () ); }); +test("ask-user-questions source-level: tryRemoteQuestions is called before the hasUI guard", () => { + // Regression test for #3480 — remote questions were silently skipped in interactive + // mode because tryRemoteQuestions was gated behind `if (!ctx.hasUI)`. + // The fix moved the remote call before that guard so configured channels + // (Telegram/Slack/Discord) fire regardless of UI availability. + const src = readFileSync( + join(__dirname, "..", "..", "ask-user-questions.ts"), + "utf-8", + ); + + const remoteCallIdx = src.indexOf("tryRemoteQuestions(params.questions"); + const hasUIGuardIdx = src.indexOf("if (!ctx.hasUI)"); + + assert.ok(remoteCallIdx !== -1, "tryRemoteQuestions call should exist in ask-user-questions.ts"); + assert.ok(hasUIGuardIdx !== -1, "!ctx.hasUI guard should exist in ask-user-questions.ts"); + assert.ok( + remoteCallIdx < hasUIGuardIdx, + "tryRemoteQuestions must be called before the !ctx.hasUI guard — otherwise remote questions are skipped in interactive mode", + ); +}); + test("config source-level: removeProviderToken uses auth.remove not auth.set with empty key", () => { const commandSrc = readFileSync( join(__dirname, "..", "..", "remote-questions", "remote-command.ts"),