diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f05c324..51005576 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
+- Fixed ChatGPT Subscription token refresh hanging indefinitely (freezing the session) when a profile's custom HTTPS/HTTP proxy hostname was slow or unreachable to resolve via DNS
- Fixed a choice-dialog prompt (e.g. a Bash-command permission prompt) flickering and repeatedly resizing when Claude's response text re-wrapped to a slightly different width after the dialog first appeared
# 1.3.12 (2026-09-27)
diff --git a/pom.xml b/pom.xml
index 8ecbe430..98a0e36c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.github.nbplugins
netbeans-plugin-claude-code-gui
- 1.3.32-SNAPSHOT
+ 1.3.33-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/ProxyConfiguration.java b/src/main/java/io/github/nbplugins/claudecodegui/settings/ProxyConfiguration.java
index 718d7289..82973127 100644
--- a/src/main/java/io/github/nbplugins/claudecodegui/settings/ProxyConfiguration.java
+++ b/src/main/java/io/github/nbplugins/claudecodegui/settings/ProxyConfiguration.java
@@ -63,7 +63,7 @@ public HttpClient.Builder applyTo(HttpClient.Builder builder) {
try {
URI uri = URI.create(url);
int port = uri.getPort() > 0 ? uri.getPort() : 8080;
- builder.proxy(ProxySelector.of(new InetSocketAddress(uri.getHost(), port)));
+ builder.proxy(ProxySelector.of(InetSocketAddress.createUnresolved(uri.getHost(), port)));
} catch (Exception e) {
builder.proxy(ProxySelector.getDefault());
}
diff --git a/src/test/java/io/github/nbplugins/claudecodegui/settings/ProxyConfigurationTest.java b/src/test/java/io/github/nbplugins/claudecodegui/settings/ProxyConfigurationTest.java
new file mode 100644
index 00000000..d087b1e0
--- /dev/null
+++ b/src/test/java/io/github/nbplugins/claudecodegui/settings/ProxyConfigurationTest.java
@@ -0,0 +1,105 @@
+package io.github.nbplugins.claudecodegui.settings;
+
+import io.github.nbplugins.claudecodegui.settings.ClaudeProfile.ProxyMode;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.time.Duration;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link ProxyConfiguration}.
+ */
+class ProxyConfigurationTest {
+
+ // -------------------------------------------------------------------------
+ // applyTo — CUSTOM proxy must not eagerly resolve the proxy hostname
+ // -------------------------------------------------------------------------
+
+ /**
+ * Regression: {@code new InetSocketAddress(host, port)} resolves the hostname via
+ * a blocking DNS lookup at construction time. If the proxy host cannot be resolved
+ * (unreachable DNS, typo, VPN down), this hangs the calling thread indefinitely —
+ * observed as {@link io.github.nbplugins.claudecodegui.chatgptauth.ChatGptTokenManager#refresh}
+ * hanging forever in {@code ChatGptTokenManagerTest}. The proxy address must be built
+ * with {@link InetSocketAddress#createUnresolved} so resolution is deferred to actual
+ * connection time (subject to the HTTP client's own connect timeout).
+ */
+ @Test
+ void applyTo_customProxy_doesNotEagerlyResolveHostname() {
+ ProxyConfiguration cfg = new ProxyConfiguration(
+ ProxyMode.CUSTOM, "", "http://proxy.example.invalid:3128", "");
+
+ HttpClient client = cfg.applyTo(HttpClient.newBuilder()).build();
+
+ assertTrue(client.proxy().isPresent(), "proxy must be configured");
+ InetSocketAddress addr = (InetSocketAddress) client.proxy().get()
+ .select(URI.create("https://api.openai.com")).get(0).address();
+
+ assertTrue(addr.isUnresolved(),
+ "proxy address must be unresolved — eager resolution can hang the calling thread");
+ assertEquals("proxy.example.invalid", addr.getHostString());
+ assertEquals(3128, addr.getPort());
+ }
+
+ /**
+ * Regression reproduction of the actual hang: an existing, real hostname whose DNS
+ * query hangs (rather than failing fast with NXDOMAIN like a made-up TLD does) must
+ * not block {@code applyTo}. This is the exact hostname used in the original hanging
+ * test ({@code ChatGptTokenManagerTest.refresh_usesHttpClientConfiguredWithProfileProxySettings}).
+ * On the old {@code new InetSocketAddress(host, port)} code this call hangs
+ * indefinitely in sandboxes where DNS queries for real-but-unreachable hosts are
+ * dropped rather than rejected; {@code assertTimeoutPreemptively} turns that hang
+ * into a fast test failure instead of stalling the whole suite.
+ */
+ @Test
+ void applyTo_customProxy_doesNotHangOnSlowOrDroppedDnsQuery() {
+ ProxyConfiguration cfg = new ProxyConfiguration(
+ ProxyMode.CUSTOM, "", "http://proxy.example.com:3128", "");
+
+ assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
+ HttpClient client = cfg.applyTo(HttpClient.newBuilder()).build();
+ InetSocketAddress addr = (InetSocketAddress) client.proxy().orElseThrow()
+ .select(URI.create("https://api.openai.com")).get(0).address();
+ assertTrue(addr.isUnresolved(), "proxy address must be unresolved");
+ });
+ }
+
+ @Test
+ void applyTo_customProxy_defaultPortWhenNoneSpecified() {
+ ProxyConfiguration cfg = new ProxyConfiguration(
+ ProxyMode.CUSTOM, "", "http://proxy.example.invalid", "");
+
+ HttpClient client = cfg.applyTo(HttpClient.newBuilder()).build();
+
+ InetSocketAddress addr = (InetSocketAddress) client.proxy().orElseThrow()
+ .select(URI.create("https://api.openai.com")).get(0).address();
+ assertEquals(8080, addr.getPort());
+ }
+
+ @Test
+ void applyTo_customProxy_fallsBackToHttpProxyWhenHttpsProxyBlank() {
+ ProxyConfiguration cfg = new ProxyConfiguration(
+ ProxyMode.CUSTOM, "http://proxy.example.invalid:3129", "", "");
+
+ HttpClient client = cfg.applyTo(HttpClient.newBuilder()).build();
+
+ InetSocketAddress addr = (InetSocketAddress) client.proxy().orElseThrow()
+ .select(URI.create("https://api.openai.com")).get(0).address();
+ assertEquals("proxy.example.invalid", addr.getHostString());
+ assertEquals(3129, addr.getPort());
+ }
+
+ @Test
+ void applyTo_noProxy_disablesProxying() {
+ ProxyConfiguration cfg = new ProxyConfiguration(ProxyMode.NO_PROXY, "", "", "");
+
+ HttpClient client = cfg.applyTo(HttpClient.newBuilder()).build();
+
+ boolean noProxySelected = client.proxy().isEmpty() || client.proxy().get()
+ .select(URI.create("https://api.openai.com")).stream()
+ .allMatch(p -> p.type() == java.net.Proxy.Type.DIRECT);
+ assertTrue(noProxySelected, "NO_PROXY must not select any proxy address");
+ }
+}