diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce97ff50..0b07065c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
+- Fixed session tab showing the old project display name after IDE restart (tab now refreshes its name once all open projects finish loading)
- Fixed model discovery failing when Enter was absorbed as a newline in the prompt instead of submitting the /model command (race condition under load)
# 0.22.24 (2026-04-29)
diff --git a/pom.xml b/pom.xml
index 34903f32..a9243be5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,9 +6,9 @@
io.github.nbclaudecodegui
netbeans-claude-code-gui
- 0.22.34-SNAPSHOT
+ 0.22.39-SNAPSHOT
nbm
- Netbeans Claude Code GUI
+ Netbeans Plugin Claude Code GUI
NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.
https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui
diff --git a/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java b/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java
index e6d7ad64..cc407472 100644
--- a/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java
+++ b/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java
@@ -44,7 +44,16 @@
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
+import java.beans.PropertyChangeListener;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import org.netbeans.api.options.OptionsDisplayer;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectInformation;
+import org.netbeans.api.project.ui.OpenProjects;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.RequestProcessor;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
@@ -201,6 +210,15 @@ public class ClaudeSessionTab extends TopComponent
/** Guards programmatic combo updates to prevent feedback through the action listener. */
private boolean updatingCombo = false;
+ /** Set to true when the tab is closed, to suppress deferred display-name updates. */
+ private volatile boolean tabClosed = false;
+
+ /** Direct ProjectInformation (not proxy) for the current session dir; used to listen for name changes. */
+ private ProjectInformation trackedProjectInfo;
+ private PropertyChangeListener projectNameListener;
+ /** Counter to cancel superseded polling rounds when updateDisplayName is called again. */
+ private final AtomicInteger displayNameGeneration = new AtomicInteger(0);
+
// -------------------------------------------------------------------------
// Constructors
// -------------------------------------------------------------------------
@@ -404,6 +422,13 @@ protected void componentClosed() {
try { pendingDiffOnClose.run(); } catch (Exception ex) { /* ignore */ }
pendingDiffOnClose = null;
}
+ tabClosed = true;
+ displayNameGeneration.incrementAndGet(); // cancel pending polls
+ if (trackedProjectInfo != null && projectNameListener != null) {
+ trackedProjectInfo.removePropertyChangeListener(projectNameListener);
+ trackedProjectInfo = null;
+ projectNameListener = null;
+ }
stopProcess();
}
@@ -1386,9 +1411,69 @@ private List getScreenLines() {
}
}
+ private static final RequestProcessor DISPLAY_NAME_RP =
+ new RequestProcessor("ClaudeSessionTab-displayName", 1, true);
+
private void updateDisplayName(File dir) {
+ // Cancel any previous generation of polling / listeners.
+ int gen = displayNameGeneration.incrementAndGet();
+ if (trackedProjectInfo != null && projectNameListener != null) {
+ trackedProjectInfo.removePropertyChangeListener(projectNameListener);
+ trackedProjectInfo = null;
+ projectNameListener = null;
+ }
+
String name = ClaudeSessionSelectorPanel.resolveTabLabel(dir);
setDisplayName(name);
setToolTipText(dir != null ? dir.getAbsolutePath() : "New Claude Code session");
+
+ if (dir == null) return;
+
+ // Subscribe directly to the ProjectInformation from the project's own Lookup
+ // (bypassing the proxy returned by ProjectUtils.getInformation) so we hear
+ // PROP_DISPLAY_NAME when NbMavenProject reloads the pom.xml from disk.
+ for (Project p : OpenProjects.getDefault().getOpenProjects()) {
+ File projDir = FileUtil.toFile(p.getProjectDirectory());
+ if (dir.equals(projDir)) {
+ ProjectInformation pi = p.getLookup().lookup(ProjectInformation.class);
+ if (pi != null) {
+ trackedProjectInfo = pi;
+ projectNameListener = evt -> {
+ if (ProjectInformation.PROP_DISPLAY_NAME.equals(evt.getPropertyName())
+ && !tabClosed) {
+ SwingUtilities.invokeLater(() -> {
+ if (!tabClosed) {
+ String fresh = ClaudeSessionSelectorPanel.resolveTabLabel(dir);
+ if (!fresh.equals(getDisplayName())) {
+ setDisplayName(fresh);
+ }
+ }
+ });
+ }
+ };
+ pi.addPropertyChangeListener(projectNameListener);
+ }
+ break;
+ }
+ }
+
+ // Fallback: poll periodically (no time limit) in case PROP_DISPLAY_NAME is never fired.
+ // Maven projects load from cache initially; the real name may arrive minutes later.
+ schedulePoll(dir, gen);
+ }
+
+ private void schedulePoll(File dir, int gen) {
+ DISPLAY_NAME_RP.schedule(() -> {
+ if (tabClosed || displayNameGeneration.get() != gen) return;
+ SwingUtilities.invokeLater(() -> {
+ if (tabClosed || displayNameGeneration.get() != gen) return;
+ String fresh = ClaudeSessionSelectorPanel.resolveTabLabel(dir);
+ if (!fresh.equals(getDisplayName())) {
+ setDisplayName(fresh);
+ LOG.fine("Tab name updated by poll: " + fresh);
+ }
+ });
+ schedulePoll(dir, gen);
+ }, 15, TimeUnit.SECONDS);
}
}
diff --git a/src/test/java/io/github/nbclaudecodegui/ui/ClaudeCodeTopComponentTest.java b/src/test/java/io/github/nbclaudecodegui/ui/ClaudeCodeTopComponentTest.java
index 282bafaa..7b274761 100644
--- a/src/test/java/io/github/nbclaudecodegui/ui/ClaudeCodeTopComponentTest.java
+++ b/src/test/java/io/github/nbclaudecodegui/ui/ClaudeCodeTopComponentTest.java
@@ -68,6 +68,24 @@ void testTabTooltipSetImmediatelyOnStartSession() throws IOException {
}
}
+ @Test
+ void testTabNameRemainsStableWhenProjectsAlreadyLoaded() throws IOException {
+ // When openProjects() Future is already done (projects fully loaded),
+ // updateDisplayName must not schedule a background refresh — the name is final.
+ // Verify this by checking that a tab created for a non-project dir keeps "Claude Code".
+ File tmpDir = Files.createTempDirectory("proj-loaded-test").toFile();
+ tmpDir.deleteOnExit();
+ try {
+ ClaudeSessionTab tc = new ClaudeSessionTab();
+ tc.autoStart(tmpDir, "default");
+ // openProjects() is done in test env (no real projects) — name must be "Claude Code"
+ assertEquals("Claude Code", tc.getDisplayName(),
+ "Tab name for non-project dir must be 'Claude Code'");
+ } finally {
+ tmpDir.delete();
+ }
+ }
+
@Test
void testCanCloseWithNoActiveSession() {
ClaudeSessionTab tc = new ClaudeSessionTab();