From f5eb0cecc528ee502ad37962b35495620b9f673f Mon Sep 17 00:00:00 2001 From: Oleg Samarin Date: Wed, 29 Apr 2026 08:49:56 +0300 Subject: [PATCH] Fixed model combo not becoming active with blocking startup dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Claude starts with --continue/--resume, it may show a "Resuming session" choice dialog before the input prompt is ready. discoverModels() was triggered immediately on the STARTING→READY transition, while this dialog was still visible. The /model command sent to the PTY was blocked until the user dismissed the dialog, but by then discovery had already timed out (16×300ms) with empty results. MAX_MODEL_DISCOVERY_ATTEMPTS=1 prevented any retry, so the model combo stayed disabled for the whole session. Fix: pollScreenState() now skips discoverModels() while any choice menu is visible on screen (via detectChoiceMenu) or already shown to the user (model.getActiveChoiceMenu() != null). Once the blocking dialog is dismissed, the next poll finds a clean screen and starts discovery. Also removed `final` from the class declaration and made discoverModels() package-private to allow test subclassing (needed for the regression test). Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 1 + pom.xml | 2 +- .../controller/ClaudeSessionController.java | 15 +++++-- .../ClaudeSessionControllerTest.java | 43 +++++++++++++++++++ 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d87fbb57..4c6639e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed model combo not becoming active when a session starts with a blocking dialog (e.g. "Resuming session" prompt on --continue/--resume startup) - Improved keyboard navigation in choice menus: arrow keys, Tab, Shift+Tab - Added terminal font and font size settings (Tools → Options → Claude Code → General); defaults to auto-detection preferring Adwaita Mono for full Unicode coverage of Claude Code TUI symbols (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/18) - Reorganized General and Advanced settings tabs: moved technical settings (hang timeout, MCP port, Enable MCP integration, session list limit, debug mode) to Advanced diff --git a/pom.xml b/pom.xml index a8aebd31..f70cc2c8 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbclaudecodegui netbeans-claude-code-gui - 0.22.31-SNAPSHOT + 0.22.32-SNAPSHOT nbm Netbeans Claude Code GUI NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE. diff --git a/src/main/java/io/github/nbclaudecodegui/controller/ClaudeSessionController.java b/src/main/java/io/github/nbclaudecodegui/controller/ClaudeSessionController.java index 365bee5e..d7397700 100644 --- a/src/main/java/io/github/nbclaudecodegui/controller/ClaudeSessionController.java +++ b/src/main/java/io/github/nbclaudecodegui/controller/ClaudeSessionController.java @@ -48,7 +48,7 @@ * direct widget references after {@link #startProcess} returns. * */ -public final class ClaudeSessionController { +public class ClaudeSessionController { // ------------------------------------------------------------------------- // Fields @@ -763,7 +763,7 @@ public void switchModel(int index) { * Retries up to {@value #MAX_MODEL_DISCOVERY_ATTEMPTS} times if the screen * returns no models. */ - private void discoverModels() { + void discoverModels() { if (connector == null || modelComboPopulated || modelDiscoveryInProgress) return; if (modelDiscoveryAttempts >= MAX_MODEL_DISCOVERY_ATTEMPTS) return; modelComboPopulated = true; @@ -923,9 +923,16 @@ void pollScreenState() { model.setLifecycle(SessionLifecycle.READY); } - // On first READY: discover models and detect initial edit mode + // On first READY: discover models and detect initial edit mode. + // Defer discovery while any blocking dialog is visible — e.g. the + // "Resuming session" prompt shown on --continue/--resume startup. + // Sending /model while Claude is blocked by such a dialog causes the + // model menu to appear only after the dialog is dismissed, by which + // time discovery has already timed out and given up. if (model.getLifecycle() == SessionLifecycle.READY - && !modelComboPopulated && !modelDiscoveryInProgress) { + && !modelComboPopulated && !modelDiscoveryInProgress + && model.getActiveChoiceMenu() == null + && screenContentDetector.detectChoiceMenu(lines).isEmpty()) { discoverModels(); detectAndApplyInitialEditMode(lines); } diff --git a/src/test/java/io/github/nbclaudecodegui/controller/ClaudeSessionControllerTest.java b/src/test/java/io/github/nbclaudecodegui/controller/ClaudeSessionControllerTest.java index ac58407b..b11dd065 100644 --- a/src/test/java/io/github/nbclaudecodegui/controller/ClaudeSessionControllerTest.java +++ b/src/test/java/io/github/nbclaudecodegui/controller/ClaudeSessionControllerTest.java @@ -618,6 +618,49 @@ void hangNotDetectedWhenWithinTimeout() { assertNull(callbackCommand.get(), "Hang callback should NOT fire when elapsed < timeout"); } + // ------------------------------------------------------------------------- + // discoverModels deferred when blocking dialog is visible at READY + // ------------------------------------------------------------------------- + + /** + * Regression: when Claude starts with --continue/--resume, it may show a + * "Resuming session" dialog before the input prompt. If discoverModels() is + * triggered while this dialog is visible, it sends /model but Claude cannot + * process it yet (blocked by the dialog). Discovery times out, the combo + * stays unpopulated, and MAX_MODEL_DISCOVERY_ATTEMPTS prevents any retry. + * + * Fix: pollScreenState must skip discoverModels() while any choice menu is + * visible on screen or already shown to the user. + */ + @Test + void pollScreenState_doesNotStartDiscoveryWhenBlockingMenuIsPresent() { + List blockingMenuScreen = Arrays.asList( + "Resuming the full session will consume a substantial portion of your usage limits.", + "❯ 1. Resume from full history", + " 2. Resume from summary", + "Enter to confirm · Esc to cancel" + ); + + java.util.concurrent.atomic.AtomicInteger discoveryCallCount = + new java.util.concurrent.atomic.AtomicInteger(0); + + ClaudeSessionModel m = new ClaudeSessionModel(); + ClaudeSessionController c = new ClaudeSessionController(m, () -> blockingMenuScreen) { + @Override + void discoverModels() { + discoveryCallCount.incrementAndGet(); + } + }; + + m.setLifecycle(SessionLifecycle.READY); + c.pollScreenState(); + + assertEquals(0, discoveryCallCount.get(), + "discoverModels must not be called while a blocking dialog is visible on screen"); + assertNotNull(m.getActiveChoiceMenu(), + "Blocking dialog must be shown to the user while discovery is deferred"); + } + // ------------------------------------------------------------------------- // No-op listener base class // -------------------------------------------------------------------------