Skip to content

Launch/Relaunch: stop opening duplicate terminals, stop the no-workspace relaunch loop, show the panel from the Tools menu - #26

Open
DaveTseng2019 wants to merge 5 commits into
firish:mainfrom
DaveTseng2019:fix/launch-guards
Open

Launch/Relaunch: stop opening duplicate terminals, stop the no-workspace relaunch loop, show the panel from the Tools menu#26
DaveTseng2019 wants to merge 5 commits into
firish:mainfrom
DaveTseng2019:fix/launch-guards

Conversation

@DaveTseng2019

Copy link
Copy Markdown

Re-submission of the launch-path fixes from the now-closed #24, split out so each PR is one reviewable topic. All four were found while using the extension.

The bugs

1. Duplicate terminals (fix: guard Launch Claude Code against duplicate terminals)
The panel's "Launch Claude Code" button never checked whether a session was already connected, so every press opened another terminal and another claude process. LaunchClaudeAsync now no-ops (with one log line) when _server.HasConnections.

The warning card's "Relaunch Claude Code" button means something different - "this connection is pinned to the wrong folder, give me a correct one" - so it needs to bypass that guard. It gets its own BridgeStatus.RelaunchAction with forceRelaunch: true.

2. Endless relaunch loop when no folder is open (fix: don't relaunch into an endless loop when no workspace is open)
The same warning card promises the relaunch "pins the right folder", but with no folder/workspace open in VS there is no right folder to pin. Each relaunch opened another terminal that tripped the same warning, so the user kept clicking. LaunchClaudeAsync now refuses when forceRelaunch is set and GetWorkspaceRootAsync() comes back empty, and says to open a folder first.

3. External console blocked by the connected-guard (fix: don't block External console when a session is already connected)
Fallout from fix 1: "External console" deliberately launches a separate, VS-surviving window, so the already-connected guard must not apply to it.

4. Tools menu now shows the panel too (feat: show the panel when launching from the Tools menu)
Tools > "Launch Claude Code" is the only entry point a first-run user finds, and it started a session without ever surfacing the panel - so the safety toggles, the attach tray and the usage stats stayed invisible unless the user also found Tools > "Claude Code Panel". ShowPanel() is factored out so both commands share it, and it runs after the fire-and-forget launch so a panel that fails to open cannot take the launch down with it.

Verification

Done in the Experimental hive: pressing Launch twice opens one terminal (activity log shows "already connected - not opening another terminal"); relaunching with no workspace open no longer spawns processes; Tools > Launch Claude Code opens the panel. Release build clean.

🤖 Generated with Claude Code

DaveTseng2019 and others added 4 commits August 8, 2026 11:13
Repeated clicks on "Launch Claude Code" each spawned a brand-new
terminal/CLI process with no check for an already-connected session.
Guard LaunchClaudeAsync behind server.HasConnections; the "hooks &
tools didn't load" banner's Relaunch button gets its own RelaunchAction
that bypasses the guard, since that flow deliberately re-pins a
misconfigured (but already connected) session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The "hooks & tools didn't load" banner's Relaunch button promises to
"pin the right folder," but if VS has no folder/workspace open there
is no folder to pin - every relaunch just spawned another equally
unpinned terminal that hit the same warning, inviting an endless
click-relaunch-fail loop (reported after live testing). Refuse and
tell the user to open a folder first instead of piling up dead
terminals.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
External console is a standalone window the user explicitly asks for each
time, and upstream allows unlimited concurrent external consoles - only the
docked Launch button should refuse to pile up redundant terminals.
Tools -> "Launch Claude Code" is the only entry point a first-run user finds,
and it started a session without ever surfacing the panel - so the toggles
(auto-accept, debugger drive, screen capture), the attach tray and the usage
stats stayed invisible until the user happened to find Tools -> "Claude Code
Panel" as well. One click now does both.

ShowPanel is factored out of OnShowPanel so both commands share it, and it runs
AFTER the fire-and-forget launch: a panel that fails to open must not take the
launch down with it. The panel's own Launch button doesn't route through here,
so it can't re-show itself.

Verified on the Exp hive: Tools -> Launch Claude Code opens the panel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 8, 2026 03:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens the Visual Studio extension’s launch/relaunch UX for the claude CLI by preventing accidental duplicate launches, avoiding a relaunch loop when no workspace is open, and ensuring the Claude panel is surfaced when launching from the Tools menu.

Changes:

  • Add a dedicated RelaunchAction path to bypass the “already connected” guard for the “hooks & tools didn’t load” warning flow.
  • Add launch guards and a no-workspace relaunch refusal to prevent redundant terminals and an endless relaunch loop.
  • Factor panel showing into a helper so Tools > Launch Claude Code also opens the panel.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/ClaudeCodeVS/Ui/ClaudeToolWindowControl.cs Updates the warning banner button to invoke the new relaunch action.
src/ClaudeCodeVS/Ui/BridgeStatus.cs Adds RelaunchAction to distinguish “re-pin” relaunch from normal launch/external launch.
src/ClaudeCodeVS/ClaudeCodeVsPackage.cs Ensures Tools > Launch Claude Code also shows the panel via a shared ShowPanel() helper.
src/ClaudeCodeVS/BridgeHost.cs Implements the connection guard exceptions (external + relaunch) and blocks relaunch when no workspace is open.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 580 to 582
/// Prefers VS's native docked Terminal; <paramref name="forceExternal"/> skips it for users who want
/// a standalone console window (which, unlike the docked tab, survives closing VS).
/// </summary>
Comment on lines +591 to +600
// Guards the plain (docked) Launch button against piling up redundant terminals on repeat clicks
// while a session is already connected. External console is exempt - it's a standalone window the
// user explicitly asked for each time, and upstream allows unlimited concurrent external consoles.
// The "hooks & tools didn't load" banner's Relaunch button passes forceRelaunch=true to bypass this
// too - that flow is a deliberate re-pin of a *misconfigured* connected session, not an accidental duplicate.
if (!forceExternal && !forceRelaunch && _server?.HasConnections == true)
{
Log.Warn("Launch Claude Code: already connected - not opening another terminal.");
return;
}
HasConnections only flips once the CLI's IDE WebSocket lands, so repeat clicks during
the launch-to-handshake window still piled up terminals. Added a 10s launch cooldown on
the same guarded path (sized to VsTerminalLauncher's stall timeout); External console and
the banner's Relaunch stay exempt, as before. Also documented forceRelaunch in the XML doc.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@DaveTseng2019

Copy link
Copy Markdown
Author

Thanks — both addressed in eecf0fa.

  • XML doc: forceRelaunch is now documented on LaunchClaudeAsync (it's the "hooks & tools didn't load" banner's Relaunch deliberately re-pinning a misconfigured session, i.e. exactly the case the guard would otherwise block; it refuses instead when no workspace is open).
  • Still-connecting race: real bug, you're right — HasConnections only flips once the CLI's WebSocket lands, so repeat clicks during the launch-to-handshake window still piled up terminals. Added a 10s launch cooldown on the same guarded path (sized to VsTerminalLauncher's stall timeout). External console and the banner's Relaunch stay exempt. Read/written on the UI thread only and before any await, so no synchronization needed. It's a cooldown rather than a real handshake because the CLI gives us no "starting" signal to await on — noted in the comment.

Verified in the Experimental hive: two Launch invocations 1.5s apart now produce exactly one claude session instead of two.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants