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
// -------------------------------------------------------------------------