From 8cdf82ebfe9cce9973bccbc75cc4680d431aa22f Mon Sep 17 00:00:00 2001 From: Oleg Samarin Date: Mon, 13 Apr 2026 20:06:15 +0300 Subject: [PATCH] =?UTF-8?q?Fixed=20Default=20profile=20settings=20not=20sa?= =?UTF-8?q?ving=20in=20Tools=20=E2=86=92=20Options=20=E2=86=92=20Profiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously saveProfiles() discarded all Default profile fields except extraCliArgs; getProfiles() only restored that one field. Now the full Default profile is serialised to a dedicated NbPreferences key (KEY_DEFAULT_PROFILE) and restored on load, with extraCliArgs still written to the legacy key for back-compat. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 1 + pom.xml | 2 +- .../settings/ClaudeProfileStore.java | 39 +++++++++- .../settings/ClaudeProfileStoreTest.java | 77 +++++++++++++++++++ 4 files changed, 116 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3515a80c..35569262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed Default profile settings (API key, connection type, proxy, etc.) not being saved when edited in Tools → Options → Claude Code → Profiles - Fixed MCP config not working on Windows due to quote stripping in CreateProcess (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/13) - Improved error panel on process start failure: shows Working Directory and CLAUDE_CONFIG_DIR fields (before Command and Error) for easier diagnosis; all fields have a right-click Copy menu; fixed "Restart Advanced…" not dismissing the error panel - Added Advanced tab in Tools → Options → Claude Code with Debug mode, Hang timeout, and a new "Enable MCP integration" toggle (disabling it skips the --mcp-config flag while hooks remain active) diff --git a/pom.xml b/pom.xml index 7a1e19f9..76c0ac7c 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbclaudecodegui netbeans-claude-code-gui - 0.21.39-SNAPSHOT + 0.21.40-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/settings/ClaudeProfileStore.java b/src/main/java/io/github/nbclaudecodegui/settings/ClaudeProfileStore.java index 6b559969..ccfa8a60 100644 --- a/src/main/java/io/github/nbclaudecodegui/settings/ClaudeProfileStore.java +++ b/src/main/java/io/github/nbclaudecodegui/settings/ClaudeProfileStore.java @@ -34,6 +34,9 @@ public final class ClaudeProfileStore { /** NbPreferences key under which the JSON array is stored. */ public static final String KEY_PROFILES = "profiles"; + /** NbPreferences key under which the full Default profile JSON is stored. */ + private static final String KEY_DEFAULT_PROFILE = "defaultProfile"; + private static final ObjectMapper MAPPER = new ObjectMapper() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); private static final TypeReference> LIST_TYPE = @@ -56,7 +59,32 @@ private ClaudeProfileStore() {} public static List getProfiles() { List result = new ArrayList<>(); ClaudeProfile def = ClaudeProfile.createDefault(); - def.setExtraCliArgs(ClaudeCodePreferences.getDefaultExtraCliArgs()); + + String defaultJson = NbPreferences.forModule(ClaudeProfileStore.class) + .get(KEY_DEFAULT_PROFILE, null); + if (defaultJson != null) { + try { + ClaudeProfile stored = MAPPER.readValue(defaultJson, ClaudeProfile.class); + def.setToken(stored.getToken()); + def.setApiKey(stored.getApiKey()); + def.setBaseUrl(stored.getBaseUrl()); + def.setProxyMode(stored.getProxyMode()); + def.setHttpProxy(stored.getHttpProxy()); + def.setHttpsProxy(stored.getHttpsProxy()); + def.setNoProxy(stored.getNoProxy()); + def.setExtraEnvVars(stored.getExtraEnvVars()); + def.setExtraCliArgs(stored.getExtraCliArgs()); + def.setModelAliases(stored.getModelAliases()); + def.setCustomModels(stored.getCustomModels()); + def.setStorageDir(stored.getStorageDir()); + } catch (Exception e) { + LOG.warning("Could not parse stored Default profile JSON, using defaults. Cause: " + e); + def.setExtraCliArgs(ClaudeCodePreferences.getDefaultExtraCliArgs()); + } + } else { + def.setExtraCliArgs(ClaudeCodePreferences.getDefaultExtraCliArgs()); + } + result.add(def); String json = NbPreferences.forModule(ClaudeProfileStore.class) @@ -108,7 +136,14 @@ public static void saveProfiles(List profiles) { if (profiles != null) { for (ClaudeProfile p : profiles) { if (p.isDefault()) { - ClaudeCodePreferences.setDefaultExtraCliArgs(p.getExtraCliArgs()); + ClaudeCodePreferences.setDefaultExtraCliArgs(p.getExtraCliArgs()); // back-compat + try { + String defaultJson = MAPPER.writeValueAsString(p); + NbPreferences.forModule(ClaudeProfileStore.class) + .put(KEY_DEFAULT_PROFILE, defaultJson); + } catch (Exception e) { + LOG.severe("Could not serialise Default profile: " + e.getMessage()); + } } else { toSave.add(p); } diff --git a/src/test/java/io/github/nbclaudecodegui/settings/ClaudeProfileStoreTest.java b/src/test/java/io/github/nbclaudecodegui/settings/ClaudeProfileStoreTest.java index 35d1bba1..063060b5 100644 --- a/src/test/java/io/github/nbclaudecodegui/settings/ClaudeProfileStoreTest.java +++ b/src/test/java/io/github/nbclaudecodegui/settings/ClaudeProfileStoreTest.java @@ -203,6 +203,83 @@ void extraCliArgs_jacksonRoundTrip() throws Exception { assertEquals("--verbose --model foo", loaded.get(0).getExtraCliArgs()); } + // ------------------------------------------------------------------------- + // Default profile full round-trip (bug: all fields except extraCliArgs were lost) + // ------------------------------------------------------------------------- + + @Test + void defaultProfile_allFields_roundTrip() { + try { + ClaudeProfile def = ClaudeProfile.createDefault(); + def.setApiKey("sk-api-key-123"); + def.setToken("oauth-token-xyz"); + def.setBaseUrl("https://custom.api.example.com"); + def.setProxyMode(ClaudeProfile.ProxyMode.CUSTOM); + def.setHttpProxy("http://proxy.example.com:8080"); + def.setHttpsProxy("https://proxy.example.com:8443"); + def.setNoProxy("localhost,127.0.0.1"); + java.util.List defEnvVars = new java.util.ArrayList<>(); + defEnvVars.add(new String[]{"FOO", "bar"}); + def.setExtraEnvVars(defEnvVars); + def.setExtraCliArgs("--verbose"); + + ClaudeProfileStore.saveProfiles(java.util.List.of(def)); + + List loaded = ClaudeProfileStore.getProfiles(); + ClaudeProfile loadedDef = loaded.get(0); + assertTrue(loadedDef.isDefault()); + assertEquals("sk-api-key-123", loadedDef.getApiKey()); + assertEquals("oauth-token-xyz", loadedDef.getToken()); + assertEquals("https://custom.api.example.com", loadedDef.getBaseUrl()); + assertEquals(ClaudeProfile.ProxyMode.CUSTOM, loadedDef.getProxyMode()); + assertEquals("http://proxy.example.com:8080", loadedDef.getHttpProxy()); + assertEquals("https://proxy.example.com:8443", loadedDef.getHttpsProxy()); + assertEquals("localhost,127.0.0.1", loadedDef.getNoProxy()); + assertEquals("--verbose", loadedDef.getExtraCliArgs()); + assertEquals(1, loadedDef.getExtraEnvVars().size()); + assertArrayEquals(new String[]{"FOO", "bar"}, (Object[]) loadedDef.getExtraEnvVars().get(0)); + } catch (Exception e) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "NbPreferences not available"); + } + } + + @Test + void namedProfile_allFields_roundTrip() { + try { + ClaudeProfile p = ClaudeProfile.createNamed("Work"); + p.setApiKey("sk-named-key"); + p.setToken("named-token"); + p.setBaseUrl("https://api.work.example.com"); + p.setProxyMode(ClaudeProfile.ProxyMode.CUSTOM); + p.setHttpProxy("http://work-proxy:8080"); + p.setHttpsProxy("https://work-proxy:8443"); + p.setNoProxy("internal.example.com"); + java.util.List namedEnvVars = new java.util.ArrayList<>(); + namedEnvVars.add(new String[]{"ENV_VAR", "value1"}); + p.setExtraEnvVars(namedEnvVars); + p.setExtraCliArgs("--model claude-opus"); + + ClaudeProfileStore.saveProfiles(java.util.List.of(ClaudeProfile.createDefault(), p)); + + List loaded = ClaudeProfileStore.getProfiles(); + assertEquals(2, loaded.size()); + ClaudeProfile loadedP = loaded.get(1); + assertEquals("Work", loadedP.getName()); + assertEquals("sk-named-key", loadedP.getApiKey()); + assertEquals("named-token", loadedP.getToken()); + assertEquals("https://api.work.example.com", loadedP.getBaseUrl()); + assertEquals(ClaudeProfile.ProxyMode.CUSTOM, loadedP.getProxyMode()); + assertEquals("http://work-proxy:8080", loadedP.getHttpProxy()); + assertEquals("https://work-proxy:8443", loadedP.getHttpsProxy()); + assertEquals("internal.example.com", loadedP.getNoProxy()); + assertEquals("--model claude-opus", loadedP.getExtraCliArgs()); + assertEquals(1, loadedP.getExtraEnvVars().size()); + assertArrayEquals(new String[]{"ENV_VAR", "value1"}, (Object[]) loadedP.getExtraEnvVars().get(0)); + } catch (Exception e) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "NbPreferences not available"); + } + } + // ------------------------------------------------------------------------- // resolveStorageDir // -------------------------------------------------------------------------