[BUG] VS Code extension: focus ping-pongs between two visible Claude panels when the window regains focus (refs #71809, #79770)

Status Open
Reported on v2.1.251
Maintainer reply None cached
Activity 2 comments · opened Aug 31, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code
Refiling per the stale-bot's instruction on #71809 ("Please open a new issue if this is still relevant"). Still reproducible on 2.1.251. Also reported as Bug 3 of #79770. This report adds the root cause from the shipped bundle.

What's Wrong?

When two Claude Code webview panels are visible at the same time — i.e. in two different editor groups — and the VS Code window loses and then regains OS focus, the two panels get stuck in a rapid mutual focus-steal loop. The input box visibly flickers between the two tabs and typing is impossible until you click somewhere to break it.

Root cause. Each Claude panel registers a window-level focus listener that restores focus to its own composer, and its only guard is document.activeElement:

// webview/index.js (2.1.251, minified)
Z1(() => {
  if (!A || !D) return;
  let a1 = () => {
    if (!document.activeElement || document.activeElement === document.body)
      x1.current?.focus();          // x1 = composer ref
  };
  let G0 = () => { setTimeout(a1, 0) };
  window.addEventListener("focus", G0);
  return () => window.removeEventListener("focus", G0);
}, [A, D]);

A near-identical second handler exists alongside it:

let y = () => { setTimeout(() => {
  let w = document.activeElement, S = <messagesContainer class>;
  if (!w || w === document.body || (S !== undefined && w.classList.contains(S))) i.emit("")
}, 0) };
window.addEventListener("focus", y);

Each webview panel is its own iframe with its own document. Focus is exclusive across the window, but document.activeElement is per-document — so in whichever panel does not currently hold focus, document.activeElement is body, the guard passes, and that panel steals focus. That flips the condition in the other panel, which steals it back:

  1. Window regains focus → every visible Claude webview's window gets a focus event.
  2. Panel A focuses its composer → panel B's activeElement falls back to body.
  3. B's handler passes the guard → B focuses its composer → A's activeElement falls back to body.
  4. A's iframe regains focus, firing focus again → back to step 2.

The setTimeout(..., 0) in both handlers keeps the two panels' cycles offset, which sustains the loop instead of letting it settle.

Why the existing guard doesn't help. The app has

safeFocus(e) { if (this.comms.connection.value?.isVisible.value) e.focus() }

but isVisible comes from WebviewPanel.visible, and with split editor groups the active tab of each visible group reports visible === true. So both panels pass. There is no "am I the focused panel" signal in the webview at all: the extension does track it (onDidChangeViewState → if (e.active) this.setActivePanel(e), and panel.active is true for exactly one panel), but the visibility_changed message sent to the webview carries only isVisible:

notifyVisibilityChange(e) {
  this.send({ ..., request: { type: "visibility_changed", isVisible: e } });
  if (e) this.checkAndUpdateReviewUpsellBanner();
}

and the webview side only stores it:

case "visibility_changed":
  this.isVisible.value = e.request.isVisible;
  ...

So the webview cannot distinguish "I am visible" from "I am the active panel", and every visible panel believes it should own focus.

What Should Happen?

Only the active panel should restore focus to its composer. The rest should leave focus alone, no matter how many panels are visible.

Error Messages/Logs

No errors are logged — this is a pure focus-management loop, nothing throws.

Steps to Reproduce

  1. Open two Claude Code sessions as editor tabs and split the editor so both are visible at once in two different editor groups.
  2. Click into one of the Claude panels' input box so it holds keyboard focus.
  3. Switch to another application (Cmd+Tab / Alt+Tab), then switch back to VS Code.
  4. Focus rapidly alternates between the two panels' input boxes; the caret and input box flicker and typing is disrupted.

Workaround: keep the Claude tabs in the same editor group. Only one tab per group is visible, so the two panels never compete. Making one group show a non-Claude file tab has the same effect. (#71809 also notes one-session-per-window and claudeCode.useTerminal: true as workarounds.)

Suggested Fix

Include panel.active in the visibility_changed payload (the extension already computes it in onDidChangeViewState), expose it as e.g. isActive next to isVisible, and gate both window-focus handlers on it. Checking document.hasFocus() alone is not sufficient — it is true in every iframe of the focused window.

Is this a regression?

I don't know

Last Working Version

Not known to have ever worked. #71809 reported it on 2.1.190–2.1.195, #79770 on 2.1.202; still present in 2.1.251.

Claude Code Version

VS Code extension anthropic.claude-code 2.1.251

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Other — VS Code extension native panel (not the integrated terminal)

Additional Information

VS Code 1.134.0, macOS (arm64). Previously reported and still unresolved:

  • #71809 — same symptom, closed by the stale bot with a note to refile; a comment there describes the same window-refocus trigger with split editor groups, plus a second in-window trigger (accepting a proposed diff that opened on top of the other panel's group).
  • #79770 (Bug 3) — "rapid focus ping-pong between tabs", open.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗