[BUG] VS Code webview stuck on "Thinking" — sessionID discarded on restore (v2.1.141, regression of #35004)

Resolved 💬 3 comments Opened May 14, 2026 by MorganStair Closed Jun 26, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

In the Claude Code VS Code extension's webview chat panel, after sending a message the UI sometimes gets stuck on the "Thinking" indicator indefinitely — the streaming UI never transitions to showing the response, even though the response is generated normally on the backend. This is happening to me during ordinary use of the extension, not in any contrived setup. I have hit it organically multiple times; I was not reloading the window, restarting the extension, or doing anything unusual. The first couple of times I assumed the response was just slow, but it never arrived and the "Thinking" indicator stayed forever.

Closing the conversation tab and reopening it from the sessions list reveals the full response on disk: the CLI completed normally and the assistant turn was persisted to ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl, but the live webview UI never received the streaming updates and stayed on "Thinking". Only the webview-to-extension binding is broken; nothing else fails.

Workaround: Close the conversation tab and reopen it from the sessions list. Your reply MAY be there with no data loss. I've seen it end up with the session stuck forever (unrecoverable), and I've seen the session with all of the data and able to continue working.

The exact natural trigger isn't pinpointed — it's some webview-lifecycle event during or after streaming (visibility change, focus change, panel layout shift, or similar). Investigation of the bundled extension.js shows two specific call sites where the saved sessionID is discarded (U.setupPanel(w, void 0, void 0, I) in deserializeWebviewPanel, and this.getHtmlForWebview(z.webview, void 0, void 0, !0) in resolveWebviewView) — see Additional Information. Any lifecycle event that re-runs either of those handlers will leave the new webview with sessionId === undefined and unable to subscribe to the in-flight session.

This matches the long-running cluster around closed/locked issues #35004, #42504, and #35022 — the same minified void 0 argument-discard pattern documented there is present in v2.1.141 (and v2.1.140). It is distinct from open issue #45729 — see "Why this is NOT #45729" in Additional Information.

What Should Happen?

After the webview is re-deserialized (panel-tab restore) or re-resolved (sidebar view-provider lifecycle), it should restore its session binding from the saved state (or Memento / workspaceState) and continue subscribing to the in-flight session, rather than initializing with sessionId === undefined and orphaning the conversation.

Error Messages/Logs

Live capture from a controlled reproduction on Windows 11, v2.1.141 win32-x64.
Trigger sequence: open Claude Code panel webview, send a long-form prompt
("Generate a 1500-word essay on the lifecycle of honeybees..."), wait for
"Thinking" indicator to appear, then Command Palette -> "Developer: Reload
Window". After reload, the restored panel sits on "Thinking" indefinitely.

Extension Output log
(%APPDATA%/Code/logs/<timestamp>/window1/exthost/Anthropic.claude-code/Claude VSCode.log):

- Around the post-reload activation window, "Getting authentication status"
  and "OAuth tokens found in secure storage" each fire FOUR times within
  ~700 ms. Consistent with the "case init" handler in extension.js running
  for four webview surfaces on reload: claudeVSCodeSidebar primary, the
  secondary-sidebar provider, claudeVSCodeSessionsList, and the
  claudeVSCodePanel restore. So init succeeded on the extension side.
- After this burst, the file received no further entries despite the
  webview UI remaining stuck on "Thinking" for minutes afterward.
  The webview <-> extension message channel is dead while the extension
  host itself is alive.

Renderer log (same logs root, /window1/renderer.log) and VS Code's own
Developer Tools (Help -> Toggle Developer Tools) console:

- "Extension host (LocalProcess pid: 7900) is unresponsive." appears
  twice during the test window.
- "Extension host (LocalProcess pid: 7900) is responsive." appears
  twice; each unresponsiveness window resolves.
- VS Code's UNRESPONSIVE-host profiler attributes both windows to
  amazonwebservices.aws-toolkit-vscode (one example:
  "amazonwebservices.aws-toolkit-vscode took 78.13% of 1366.638ms"),
  NOT to anthropic.claude-code. Claude Code's extension host stays
  healthy throughout the hang. This is the diagnostic shape that
  distinguishes the bug from #45729, where the extension host itself
  becomes unresponsive after Claude Code's init handler stalls.

Session JSONL on disk
(~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl):

- Contains the user prompt entry at the time the message was sent,
  plus the deferred-tool listing and skill-listing attachments that
  Claude Code persists at session start.
- In this controlled reproduction, the JSONL does NOT contain a
  completed assistant response, because "Developer: Reload Window"
  terminates the extension host process and that termination
  cascades to the CLI subprocess mid-stream. Under natural triggers
  (the originally-experienced version of this bug, where a webview
  lifecycle event destroys and recreates the webview without
  killing the host), the CLI continues running and the JSONL is
  fully populated by the time the user reopens the conversation
  tab. The Ctrl+R reproduction is a stronger version of the same
  underlying sessionID-discard defect; it also kills the CLI as
  a side effect, but the webview's "stuck on Thinking" symptom is
  identical in both cases.

Webview-side DevTools console (Developer: Open Webview Developer Tools)
was not captured for this report. Happy to capture and attach if helpful.

Steps to Reproduce

Naturally-occurring repro (how I actually hit this):

  1. Open VS Code with the Claude Code extension installed (v2.1.141 on win32-x64).
  2. Open Claude Code in panel webview mode (defaults: claudeCode.preferredLocation: "panel", claudeCode.useTerminal: false).
  3. Use Claude Code normally. Send prompts. Read responses.
  4. At some point, after sending a message, the "Thinking" indicator appears and never goes away. I have not pinpointed the exact trigger — I may have briefly changed focus to another editor, changed the panel layout, or otherwise caused a webview re-render during streaming. Nothing unusual. I have hit this organically more than once before investigating.
  5. Closing and reopening the conversation tab from the sessions list reveals the full response that the CLI generated and persisted to disk while the webview was stuck — confirming the disconnect is purely on the webview-to-extension binding.

The natural trigger is intermittent because it requires the webview to re-render during a streaming response, which is a small window. Once a webview lifecycle event fires in that window, the new webview is constructed with sessionId === undefined (see Root Cause in Additional Information) and cannot subscribe back to the still-running CLI session.

Deterministic forcing function (for verification by a maintainer):

If the natural occurrence is hard to catch live, the following sequence reliably reproduces the same symptom by deliberately re-running deserializeWebviewPanel while a response is in flight:

  1. Steps 1–3 above.
  2. Send a prompt that streams for ~10 seconds, e.g. "Generate a 1500-word essay on the lifecycle of honeybees, from egg through queen succession, including the role of pheromones. Write the full essay; do not summarize or outline first."
  3. While the "Thinking" indicator is visible, open the Command Palette (Ctrl+Shift+P) and run Developer: Reload Window.
  4. After the reload, the restored Claude Code panel re-renders with the "Thinking" indicator still visible and stays stuck on it indefinitely.

This forcing-function repro is a stronger version of the same defect: Developer: Reload Window also terminates the extension host process and therefore the CLI subprocess, so in this case the response is not persisted to disk before the hang. The webview's "stuck on Thinking" symptom and underlying sessionID-discard code path are identical to the natural-trigger case; only the CLI lifecycle differs.

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.83

Claude Code Version

2.1.141 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Other

Additional Information

Root cause — call-site evidence in v2.1.141 extension.js

The bug is at the call sites in deserializeWebviewPanel and resolveWebviewView. setupPanel's method definition itself is fine.

extension.js v2.1.141, SHA256 23F19A6044439E67C5F2532C3FD02BB63397EE7835040A51BA114474367ABD1E, 2,158,607 bytes.

Bad call site #1 — panel-tab restore (line 882 of extension.js):

window.registerWebviewPanelSerializer("claudeVSCodePanel", {
  async deserializeWebviewPanel(w, R) {
    let v = R, I;
    if (typeof v?.isFullEditor === "boolean") I = v.isFullEditor;
    else I = window.tabGroups.all.findIndex((_) => _.viewColumn === w.viewColumn) === 0;
    U.setupPanel(w, void 0, void 0, I)   // v?.sessionID is discarded
  }
})

The saved-state object R is bound to v, its isFullEditor field is extracted correctly, but v?.sessionID is never passed to setupPanel. Variable-name map for v2.1.141: U is the manager class, w is the panel, R (= v) is the saved state, I is isFullEditor. Identical bug shape to #35004's version-by-version table, just with the v2.1.141 minifier-name rotation.

Suggested fix:

U.setupPanel(w, v?.sessionID, void 0, I)

Bad call site #2 — sidebar view provider (line 822 of extension.js, inside U.resolveWebviewView(z, K, N)):

resolveWebviewView(z, K, N) {
  let V = { isVisible: () => z.visible };
  this.webviews.add(V);
  z.webview.options = { enableScripts: !0, localResourceRoots: [...] };
  z.webview.html = this.getHtmlForWebview(z.webview, void 0, void 0, !0)   // sessionID and initialPrompt = void 0
  // ... constructs new m8(this.context, ..., void 0, ...) — that void 0 is `panelTab`, intentionally empty for sidebar
}

WebviewViewProvider.resolveWebviewView does not receive a saved-state argument like deserializeWebviewPanel does. The persisted sessionID could be retrieved from this.context.workspaceState / globalState / a Memento — but the current implementation reads no such state, so the sidebar webview always opens with sessionId === undefined.

(Note: the new m8(..., void 0, ...) in this same body is not a bug. Tracing class m8 extends Pb shows arg 15 is panelTab — legitimately empty for a sidebar webview that has no associated panel. Earlier #42504 commentary that called this slot initialSessionId was incorrect for v2.1.141's class shape.)

Good call site for contrast — createPanel (same line 822, ~5–6 KB earlier in the minified line):

let B = window.createWebviewPanel("claudeVSCodePanel", "Claude Code", x, { ... });
let O = N === ViewColumn.Active;
this.setupPanel(B, z, K, O)   // real sessionID `z` passed correctly from createPanel's args

The setupPanel method definition itself (line 822 of extension.js):

setupPanel(z, K, N, V) {
  // z = panel, K = sessionID, N = initialPrompt, V = isFullEditor
  // ...
  z.webview.html = this.getHtmlForWebview(z.webview, K, N, !1, V);   // K propagated correctly
  let H = new m8(this.context, O, this.settings, z.webview, ..., z, ..., !!V, ...);
  // ...
}

setupPanel itself correctly propagates K (sessionID) into getHtmlForWebview and into the new m8(...) communicator. The bug is purely at the two call sites above where void 0 is passed instead of the saved sessionID.

Version map (abridged — full history in #35004)

| Version | setupPanel call inside deserializeWebviewPanel | State |
|---|---|---|
| v2.1.87 (per #35004) | Z.setupPanel(A, void 0, void 0, v) | Broken |
| v2.1.89 (per #42504) | regressed, same shape | Broken |
| v2.1.140 (this install) | minifier shape: ?.setupPanel(F, void 0, void 0, I) | Broken |
| v2.1.141 (this install) | U.setupPanel(w, void 0, void 0, I) | Broken |

Variable names rotate with each minifier run; the bug shape is identical across versions. The full progression from v2.1.74 onward is documented in #35004's comment thread.

Why this is NOT #45729

#45729 reports a different hang in the same general extension. That bug's hang is in the init request handler (line 282 of extension.js):

case "init": {
  let N = this.getAuthStatus(),
      V = z.channelId ? this.channels.get(z.channelId) : void 0;
  if (this.cachedCurrentRepo === void 0) {
    let B = await this.teleportService.detectCurrentRepository();   // can block indefinitely
    if (B) { ... }
  }
  // ...
  return { type: "init_response", state: { ... } }
}

If teleportService.detectCurrentRepository() hangs (git command stalls, network probe times out, WSL pipe hangs), the init response never returns. The webview sits forever waiting; the extension host gets marked unresponsive shortly after. That fits #45729's described symptom — blank webview, no further log lines after the init message arrives, host eventually marked unresponsive — before any conversation is possible.

This bug is different: init succeeds (a conversation is established, a message is sent, a response is generated and persisted). The hang fires later, when a webview lifecycle event re-deserializes the panel or re-resolves the sidebar view without restoring the session binding. The two code paths do not overlap.

Aside: the "AuthManager initialized" log appearing twice in #45729's broken trace is not a bug signal. Tracing the three new K2(...) construction sites in v2.1.141 (K2 is the AuthManager class; the log line is in its constructor) shows that the manager class plus the first m8 communicator each construct one K2 on activation. Two AuthManager-initialized lines is the steady-state baseline for a sidebar-only setup, three for a setup with both sidebar and a restored panel tab. It's not a defect.

Related cluster
  • #35004 — analytical anchor; version-by-version variable-name table from v2.1.74 through v2.1.87 (closed/locked).
  • #42504 — v2.1.89 regression report with video evidence; first to identify that both deserializeWebviewPanel and resolveWebviewView paths are affected (closed/locked).
  • #35022 — Windows variant on v2.1.76; same root-cause family (closed/locked).
  • #55453 — Cursor-specific renderer-hang variant; related-but-distinct (open).
  • #45729 — distinct root cause (teleportService.detectCurrentRepository() hang inside the init handler); see section above (open).
  • Excluded as different root causes: #56200 (onWillSaveTextDocument settings-write hang, dup of #45729), #20960 (~/.claude/projects/ .jsonl accumulation → OOM at startup), #59090 (separate UI defect: thinking-process expander not clickable).
Environment

| Component | Value |
|---|---|
| OS | Windows 11 Pro 25H2 (OS build 26200.8457, UBR 8457) |
| VS Code | 1.120.0, commit 0958016b2af9f09bb4257e0df4a95e2f90590f9f, x64 |
| Node | v24.6.0 |
| Claude CLI | 2.1.141 |
| PowerShell | 7.6.1 |
| Extension | anthropic.claude-code v2.1.141, target platform win32-x64 |
| claudeCode.useTerminal | not set (default false → webview is in use) |
| claudeCode.preferredLocation | "panel" |
| claudeCode.allowDangerouslySkipPermissions | true |
| claudeCode.initialPermissionMode | "bypassPermissions" |

Binary hashes (to pin the bundle a maintainer is looking at):

| File | Size (bytes) | SHA256 |
|---|---|---|
| extension.js (v2.1.141) | 2,158,607 | 23F19A6044439E67C5F2532C3FD02BB63397EE7835040A51BA114474367ABD1E |
| webview/index.js (v2.1.141) | 4,798,391 | D756D1D369CFB41AD0EC620506C7C7E11B2FBF4528516B62E0375A5E658E3B13 |
| webview/index.css (v2.1.141) | 374,037 | E186CBA6F0E68E839C177ECE0879864531A536AED63AF5537BB753ECEBAA5F7D |
| extension.js (v2.1.140) | 2,154,411 | 2D82CDC5F638B42EE8FF2F3365F9AAFB3FA94FAA47C00E33C18D30FC6332E146 |

Attached files
  • vscode-console-log.txt — Renderer-process DevTools console capture (Help → Toggle Developer Tools → Console tab), saved during the controlled reproduction. Contains the Extension host (LocalProcess pid: 7900) is unresponsive. and is responsive. markers, and VS Code's UNRESPONSIVE-host profiler attributing both unresponsiveness windows to amazonwebservices.aws-toolkit-vscode (one entry: took 78.13% of 1366.638ms) — not to anthropic.claude-code. This is the distinguishing signal from #45729: Claude Code's extension host stays healthy throughout the hang.
  • live-hang-claude-vscode-log-500.txt — Last 500 lines of the Claude Code extension Output channel log (%APPDATA%/Code/logs/<timestamp>/window1/exthost/Anthropic.claude-code/Claude VSCode.log) from the same reproduction. Shows four Getting authentication status / OAuth tokens found in secure storage pairs in quick succession during the post-reload activation window (init handlers running for all four webview surfaces — sidebar primary, sidebar secondary, sessions list, restored panel tab), then complete silence in the channel for minutes while the webview UI remains stuck on "Thinking". Confirms the webview ↔ extension message channel is dead even though the extension host itself remains alive.

vscode-console-log.txt

live-hang-claude-vscode-log-500.txt

View original on GitHub ↗

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