Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Fixed MCP connection to localhost being routed through an active HTTP_PROXY/HTTPS_PROXY; NO_PROXY is now automatically extended with localhost and 127.0.0.1 when a proxy is configured
- Fixed terminal scrollbar disappearing when Claude shows a choice/confirmation prompt (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/22)
- Fixed Cancel button sending Ctrl+C instead of Esc, making it impossible to exit /usage and similar screens
- Fixed Default profile settings not saving in Tools → Options → Profiles (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/31)
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.nbclaudecodegui</groupId>
<artifactId>netbeans-claude-code-gui</artifactId>
<version>0.20.8-SNAPSHOT</version>
<version>0.20.9-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans Claude Code GUI</name>
<description>NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ public PtyProcess start(String workingDir, ClaudeProfile profile, String extraCl
+ ", apiKeyHelper=" + (apiKeyHelper ? "SET" : "NOT SET")
+ ", ANTHROPIC_AUTH_TOKEN=" + (!env.getOrDefault("ANTHROPIC_AUTH_TOKEN", "").isBlank() ? "SET" : "NOT SET")
+ ", ANTHROPIC_BASE_URL=" + env.getOrDefault("ANTHROPIC_BASE_URL", "(not set)")
+ ", CLAUDE_CONFIG_DIR=" + env.getOrDefault("CLAUDE_CONFIG_DIR", "(inherited)"));
+ ", CLAUDE_CONFIG_DIR=" + env.getOrDefault("CLAUDE_CONFIG_DIR", "(inherited)")
+ ", HTTP_PROXY=" + env.getOrDefault("HTTP_PROXY", env.getOrDefault("http_proxy", "(not set)"))
+ ", HTTPS_PROXY=" + env.getOrDefault("HTTPS_PROXY", env.getOrDefault("https_proxy", "(not set)"))
+ ", NO_PROXY=" + env.getOrDefault("NO_PROXY", env.getOrDefault("no_proxy", "(not set)")));

List<String> cmd = new ArrayList<>();
cmd.add(executable);
Expand Down Expand Up @@ -587,9 +590,36 @@ static Map<String, String> buildEnv(ClaudeProfile profile, java.nio.file.Path pr
env.putAll(profile.toEnvVars());
}

// Ensure localhost is excluded from proxy to protect MCP SSE server
ensureLocalhostInNoProxy(env);

return env;
}

private static void ensureLocalhostInNoProxy(Map<String, String> env) {
// Normalize lowercase no_proxy → NO_PROXY (Linux systems may use either)
if (!env.containsKey("NO_PROXY") && env.containsKey("no_proxy")) {
env.put("NO_PROXY", env.get("no_proxy"));
}

String httpProxy = env.getOrDefault("HTTP_PROXY", env.getOrDefault("http_proxy", ""));
String httpsProxy = env.getOrDefault("HTTPS_PROXY", env.getOrDefault("https_proxy", ""));
if (httpProxy.isBlank() && httpsProxy.isBlank()) return; // no proxy active, nothing to do

String noProxy = env.getOrDefault("NO_PROXY", "");
List<String> required = List.of("localhost", "127.0.0.1");
List<String> missing = required.stream()
.filter(h -> !noProxy.contains(h))
.collect(java.util.stream.Collectors.toList());
if (missing.isEmpty()) return;

String updated = noProxy.isBlank()
? String.join(",", missing)
: noProxy + "," + String.join(",", missing);
env.put("NO_PROXY", updated);
LOG.fine("Auto-added to NO_PROXY: " + missing + " → NO_PROXY=" + updated);
}

/**
* Builds a minimal {@code settings.local.json} from scratch for the given port.
* Used as a fallback when the existing file cannot be parsed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,49 @@ void buildEnv_systemManagedProxy_noProxyVarsAdded() {
}
}

// -------------------------------------------------------------------------
// NO_PROXY auto-injection
// -------------------------------------------------------------------------

@Test
void buildEnv_customProxy_noProxyBlank_injectsLocalhost() {
ClaudeProfile p = ClaudeProfile.createNamed("CustomProxyNoNP");
p.setProxyMode(ProxyMode.CUSTOM);
p.setHttpProxy("http://proxy:3128");
p.setHttpsProxy("");
p.setNoProxy("");
Map<String, String> env = ClaudeProcess.buildEnv(p, PROFILES_DIR);
String noProxy = env.getOrDefault("NO_PROXY", "");
assertTrue(noProxy.contains("localhost"), "NO_PROXY should contain localhost, was: " + noProxy);
assertTrue(noProxy.contains("127.0.0.1"), "NO_PROXY should contain 127.0.0.1, was: " + noProxy);
}

@Test
void buildEnv_customProxy_noProxyHasLocalhost_notDuplicated() {
ClaudeProfile p = ClaudeProfile.createNamed("CustomProxyHasNP");
p.setProxyMode(ProxyMode.CUSTOM);
p.setHttpProxy("http://proxy:3128");
p.setHttpsProxy("");
p.setNoProxy("localhost,127.0.0.1");
Map<String, String> env = ClaudeProcess.buildEnv(p, PROFILES_DIR);
String noProxy = env.getOrDefault("NO_PROXY", "");
// Should not contain duplicates
int localhostCount = noProxy.split("localhost", -1).length - 1;
assertEquals(1, localhostCount, "localhost should appear exactly once in NO_PROXY: " + noProxy);
}

@Test
void buildEnv_noProxyActive_noLocalhostInjected() {
ClaudeProfile p = ClaudeProfile.createNamed("CustomNoProxyActive");
p.setProxyMode(ProxyMode.NO_PROXY);
Map<String, String> env = ClaudeProcess.buildEnv(p, PROFILES_DIR);
// NO_PROXY should be empty (explicitly cleared by NO_PROXY mode)
// and since no proxy is set, ensureLocalhostInNoProxy should not add anything
String noProxy = env.getOrDefault("NO_PROXY", "");
assertFalse(noProxy.contains("localhost"),
"With no proxy active, localhost should not be injected: " + noProxy);
}

// -------------------------------------------------------------------------
// Extra env vars
// -------------------------------------------------------------------------
Expand Down
Loading