From da005857687d272d542a69f6a9e9a2a3a82ac69a Mon Sep 17 00:00:00 2001 From: Oleg Samarin Date: Mon, 1 Jun 2026 16:10:54 +0300 Subject: [PATCH] Fixed stale model aliases showing in model selector after profile type switch When a profile had model aliases/custom models configured under "Claude Compatible API" and was then switched to "Managed by Claude", the stored aliases were never cleared. On the next session, customModels were loaded into customModelIds and appeared in the model combo box; modelAliases were also incorrectly injected as ANTHROPIC_DEFAULT_*_MODEL env vars into the claude process. Two fixes: 1. ClaudeProfilesPanel.flushFormToCurrentProfile(): clear modelAliases and customModels when saving a connection type that does not support them (anything other than OTHER_API / OPENAI_PROXY). 2. ClaudeProfile.toEnvVars(): guard ANTHROPIC_DEFAULT_*_MODEL injection so aliases are only emitted for OTHER_API and OPENAI_PROXY connections. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 1 + pom.xml | 2 +- .../claudecodegui/settings/ClaudeProfile.java | 7 +- .../settings/ClaudeProfilesPanel.java | 7 ++ .../settings/ClaudeProfileTest.java | 32 ++++++++ .../ClaudeProfilesPanelFlushTest.java | 75 +++++++++++++++++++ 6 files changed, 121 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a38e42..df282b71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed stale model aliases from a "Claude Compatible API" profile still showing in the model selector after switching the profile to "Managed by Claude" - Fixed ClassNotFoundException on plugin installation/upgrade when the session tab had been previously saved under the old package name (https://github.com/nbplugins/NetbeansPluginClaudeCodeGui/issues/146) - Fixed a Warning dialog appearing on NetBeans exit when a diff tab had been opened during the session diff --git a/pom.xml b/pom.xml index ae7485d2..15626ccf 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbplugins netbeans-plugin-claude-code-gui - 1.2.15-SNAPSHOT + 1.2.16-SNAPSHOT nbm Netbeans Plugin 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/nbplugins/claudecodegui/settings/ClaudeProfile.java b/src/main/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfile.java index d4fb3743..acd51275 100644 --- a/src/main/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfile.java +++ b/src/main/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfile.java @@ -308,8 +308,11 @@ public Map toEnvVars() { default -> { /* SYSTEM_MANAGED — inherit from parent process */ } } - // Model aliases (ANTHROPIC_DEFAULT_*_MODEL) - if (modelAliases != null) { + // Model aliases (ANTHROPIC_DEFAULT_*_MODEL) — only for connection types + // that use a custom endpoint; irrelevant (and potentially harmful) otherwise. + ConnectionType ct = computeConnectionType(); + if ((ct == ConnectionType.OTHER_API || ct == ConnectionType.OPENAI_PROXY) + && modelAliases != null) { for (int i = 0; i < ALIAS_NAMES.length; i++) { String modelId = modelAliases.get(ALIAS_NAMES[i]); if (modelId != null && !modelId.isBlank()) { diff --git a/src/main/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfilesPanel.java b/src/main/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfilesPanel.java index 16c8b1b0..3c07ced0 100644 --- a/src/main/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfilesPanel.java +++ b/src/main/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfilesPanel.java @@ -615,6 +615,13 @@ private void flushFormToCurrentProfile() { p.setOpenaiProxy(false); } + // Clear model aliases/custom models when switching to a connection type that does not + // support them — prevents stale aliases from the old type appearing in the model combo box. + if (!rbOtherApi.isSelected() && !rbOpenAIProxy.isSelected()) { + p.setModelAliases(null); + p.setCustomModels(null); + } + // Proxy if (rbProxyNone.isSelected()) { p.setProxyMode(ProxyMode.NO_PROXY); diff --git a/src/test/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfileTest.java b/src/test/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfileTest.java index 472bb73a..a545999e 100644 --- a/src/test/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfileTest.java +++ b/src/test/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfileTest.java @@ -387,4 +387,36 @@ void storageDir_jacksonRoundTrip() throws Exception { ClaudeProfile loaded = mapper.readValue(json, ClaudeProfile.class); assertEquals("/custom/path", loaded.getStorageDir()); } + + // ------------------------------------------------------------------------- + // toEnvVars — model aliases (regression for stale-aliases-after-type-switch) + // ------------------------------------------------------------------------- + + @Test + void toEnvVars_doesNotInjectModelAliasesForManagedConnection() { + ClaudeProfile p = ClaudeProfile.createNamed("Test"); + // no apiKey / token → CLAUDE_MANAGED + p.setModelAliases(Map.of("sonnet", "old-model-id")); + p.setCustomModels(List.of("old-custom-model")); + + assertEquals(ConnectionType.CLAUDE_MANAGED, p.computeConnectionType()); + Map env = p.toEnvVars(); + + assertFalse(env.containsKey("ANTHROPIC_DEFAULT_SONNET_MODEL"), + "Model alias env vars must not be injected for CLAUDE_MANAGED connection"); + } + + @Test + void toEnvVars_injectsModelAliasesForOtherApiConnection() { + ClaudeProfile p = ClaudeProfile.createNamed("Test"); + p.setApiKey("sk-test"); + p.setBaseUrl("https://example.com"); + p.setModelAliases(Map.of("sonnet", "my-sonnet-model")); + + assertEquals(ConnectionType.OTHER_API, p.computeConnectionType()); + Map env = p.toEnvVars(); + + assertEquals("my-sonnet-model", env.get("ANTHROPIC_DEFAULT_SONNET_MODEL"), + "Model alias env vars must be injected for OTHER_API connection"); + } } diff --git a/src/test/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfilesPanelFlushTest.java b/src/test/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfilesPanelFlushTest.java index 2f6b81d9..df6f7704 100644 --- a/src/test/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfilesPanelFlushTest.java +++ b/src/test/java/io/github/nbplugins/claudecodegui/settings/ClaudeProfilesPanelFlushTest.java @@ -4,9 +4,11 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.Map; import javax.swing.JComboBox; import javax.swing.JPasswordField; import javax.swing.JRadioButton; +import javax.swing.JTextField; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -64,6 +66,79 @@ void flushWritesToFormProfile_notComboSelection() throws Exception { "Default profile must not receive Work's apiKey"); } + /** + * Regression for stale model aliases after profile type switch: + * when a profile had model aliases set under "Claude Compatible API" and then + * the user switches to "Managed by Claude", flushFormToCurrentProfile must + * clear the stored modelAliases and customModels so they don't appear in the + * model combo box in the next session. + */ + @Test + void flush_clearsModelAliasesWhenSwitchingToManaged() throws Exception { + ClaudeProfile work = ClaudeProfile.createNamed("Work"); + work.setApiKey("sk-old"); + work.setBaseUrl("https://old.example.com"); + work.setModelAliases(Map.of("sonnet", "old-model")); + work.setCustomModels(List.of("old-custom-1", "old-custom-2")); + + ClaudeProfilesPanel panel = new ClaudeProfilesPanel(); + setField(panel, "profiles", new ArrayList<>(List.of(ClaudeProfile.createDefault(), work))); + rebuildCombo(panel); + invoke(panel, "loadProfileIntoForm", ClaudeProfile.class, work); + + // Switch to "Managed by Claude" — clear API key and base URL + JPasswordField apiKeyField = getField(panel, "apiKeyField", JPasswordField.class); + apiKeyField.setText(""); + JTextField baseUrlField = getField(panel, "baseUrlField", JTextField.class); + baseUrlField.setText(""); + JRadioButton rbManaged = getField(panel, "rbManaged", JRadioButton.class); + rbManaged.setSelected(true); + // deselect others + for (String rb : new String[]{"rbSubscription", "rbClaudeApi", "rbOtherApi", "rbOpenAIProxy"}) { + getField(panel, rb, JRadioButton.class).setSelected(false); + } + + invoke(panel, "flushFormToCurrentProfile"); + + assertTrue(work.getModelAliases().isEmpty(), + "modelAliases must be cleared when switching to Managed by Claude"); + assertTrue(work.getCustomModels().isEmpty(), + "customModels must be cleared when switching to Managed by Claude"); + } + + @Test + void flush_keepsModelAliasesForOtherApiConnection() throws Exception { + ClaudeProfile work = ClaudeProfile.createNamed("Work"); + work.setApiKey("sk-test"); + work.setBaseUrl("https://example.com"); + work.setModelAliases(Map.of("sonnet", "my-sonnet")); + work.setCustomModels(List.of("custom-1")); + + ClaudeProfilesPanel panel = new ClaudeProfilesPanel(); + setField(panel, "profiles", new ArrayList<>(List.of(ClaudeProfile.createDefault(), work))); + rebuildCombo(panel); + invoke(panel, "loadProfileIntoForm", ClaudeProfile.class, work); + + // Keep "Other API" selected + JPasswordField apiKeyField = getField(panel, "apiKeyField", JPasswordField.class); + apiKeyField.setText("sk-test"); + JTextField baseUrlField = getField(panel, "baseUrlField", JTextField.class); + baseUrlField.setText("https://example.com"); + JRadioButton rbOtherApi = getField(panel, "rbOtherApi", JRadioButton.class); + rbOtherApi.setSelected(true); + for (String rb : new String[]{"rbManaged", "rbSubscription", "rbClaudeApi", "rbOpenAIProxy"}) { + getField(panel, rb, JRadioButton.class).setSelected(false); + } + + invoke(panel, "flushFormToCurrentProfile"); + + // Model aliases must NOT be cleared when staying on Other API + assertFalse(work.getModelAliases().isEmpty(), + "modelAliases must be kept when connection type supports them"); + assertFalse(work.getCustomModels().isEmpty(), + "customModels must be kept when connection type supports them"); + } + // ------------------------------------------------------------------------- // Reflection helpers // -------------------------------------------------------------------------