Hi Daniel,
I had a problem with Claude Code's usage display and had it run a diagnostic check on its own. I haven't checked it yet, but the symptom describes what I've observed.
Greetings
René
Summary
Two symptoms, one cause:
- The inline usage bars only ever show the value scraped once at startup; every subsequent
background refresh is a silent no-op.
- Opening the Claude Usage tool window after that startup cycle shows a completely blank
panel — no page, not even the "Loading…" hint.
Both happen because VS tears the WebView2 down as soon as the usage tool window frame is
hidden at the end of the startup show/hide cycle, and nothing ever re-initializes it:
Reload() and Navigate() are then called on a CoreWebView2 that no longer exists.
This is the real cause behind #111 (closed as fixed in v75.0). v75.0 only restored the
startup scrape, so after a restart it looks fixed for a moment and then freezes again —
which matches the reporter's "everything is working fine now" right after updating.
Environment
- Extension v139.0 (commit
d455399, clean checkout of master, no local changes)
- Visual Studio 18.8.12023.21 (stable)
- Windows 11 Pro 10.0.26200
- WebView2 Runtime 151.0.4129.72
- Provider: Claude Code,
ShowInlineUsageBars: true, UsageAutoRefreshSeconds: 120,
UsageWindowOpened: false (inline bars only, tool window closed)
Evidence (from a live session, 9 minutes after VS start)
| Measurement |
Value |
| devenv started |
08:18:06 |
LastUsageTimestamp in claudecode-settings.json |
08:18:25 — the single startup scrape |
| Time of measurement |
08:27 → ~4 timer ticks should have happened at 120 s |
WebView2 processes with --user-data-dir=…\ClaudeCodeExtension\WebView2 |
none |
| Last write anywhere in the WebView2 profile (Cookies, History, Network State) |
08:18:25, nothing after |
EBWebView\lockfile |
absent → browser process shut down cleanly |
| Crashpad dumps |
none → no crash, an orderly teardown |
| Opening the usage tool window afterwards |
blank panel, confirmed |
So the WebView2 is fully gone the moment frame.Hide() runs at the end of
EnsureUsageToolWindowAsync's background-init branch: VS removes the tool window content
from the visual tree, the HwndHost destroys the hosting window, and CoreWebView2 dies
with it.
Root cause
Controls/ClaudeCodeControl.Usage.cs:280 short-circuits every refresh:
if (_usageToolWindow.UsageControl?.IsWebViewInitialized == true)
{
_backgroundScrapeCompletionTcs = new TaskCompletionSource<bool>();
_usageToolWindow.UsageControl?.Reload(); // no-op: CoreWebView2 is gone
await Task.WhenAny(_backgroundScrapeCompletionTcs.Task, Task.Delay(10000)); // times out
_backgroundScrapeCompletionTcs = null;
_usageToolWindow.UsageControl?.MarkNeedsReloadOnShow();
return; // working fallback never reached
}
Two things combine:
IsWebViewInitialized => _initialized (UI/ClaudeUsageControl.xaml.cs:651) is only an
"OnLoaded has run once" flag, not a liveness check. It stays true forever, long after
CoreWebView2 has been destroyed.
Reload() (UI/ClaudeUsageControl.xaml.cs:919) is
try { WebView?.CoreWebView2?.Reload(); } catch { } — with a dead CoreWebView2 nothing
happens and nothing is reported.
Result: the working fallback (ShowNoActivate → scrape → Hide, line 295 ff.) is never
entered again after the first startup cycle. The same no-op also swallows the control's own
auto-refresh timer (RestartAutoRefreshTimer → Tick += Reload,
UI/ClaudeUsageControl.xaml.cs:909), which is why the Auto-refresh setting makes no
difference either.
Regression
Commit 27bf92a — "10.31 - Usage tab no longer blinks during background refresh". Before
that, every refresh did the full show/hide cycle, which still works today (it produces the
one startup value). It was replaced by a hidden Reload() with the assumption in the
comment that "CoreWebView2 processes navigation and JS messaging even when the frame is
hidden" — true only as long as the WebView2 is still alive, which it isn't after Hide().
Repro
-
Claude Code provider, inline usage bars on, usage tool window closed, auto-refresh on (120 s).
-
Start VS, wait for the bars to appear (startup scrape works).
-
Wait 5–10 minutes, then look at LastUsageTimestamp in
%LOCALAPPDATA%\ClaudeCodeExtension\claudecode-settings.json — it never advances.
-
Get-CimInstance Win32_Process -Filter "Name='msedgewebview2.exe'" shows no process with
the extension's --user-data-dir.
-
Now open the Claude Usage tool window → blank panel (confirmed). OnLoaded bails out
on if (_initialized) return; so InitializeWebViewAsync() never runs again,
OnWindowBecameVisible navigates a null CoreWebView2, and LoadingText was already
set to Collapsed by the first NavigationCompleted — hence not even a loading hint.
Workaround for users until this is fixed: toggle the Show Usage toolbar button off and on.
ForceClose() destroys the tool window, so the next FindToolWindow(create: true) builds a
fresh ClaudeUsageControl with a working WebView2.
Suggested fix
- Replace the
IsWebViewInitialized gate with a real liveness check
(WebView?.CoreWebView2 != null) and only take the reload short-circuit when it holds.
- When
CoreWebView2 is dead, run the startup show/hide cycle again including re-init.
Since the blank-window symptom shows the existing WebView2 element cannot be revived
after being torn out of the visual tree, this needs a fresh WebView2 instance placed
into a host container (rather than the fixed x:Name="WebView" element), plus resetting
_initialized so InitializeWebViewAsync() runs again. That also fixes the blank panel
on explicit open.
- Let
Reload() return bool so the host notices a failed reload immediately, and force a
full cycle on the next tick when the scrape wait times out (self-healing).
- Tab blinking — the reason for v10.31 — should be milder than back then, because the
fallback path now uses ShowNoActivate() instead of Show().
Hi Daniel,
I had a problem with Claude Code's usage display and had it run a diagnostic check on its own. I haven't checked it yet, but the symptom describes what I've observed.
Greetings
René
Summary
Two symptoms, one cause:
background refresh is a silent no-op.
panel — no page, not even the "Loading…" hint.
Both happen because VS tears the WebView2 down as soon as the usage tool window frame is
hidden at the end of the startup show/hide cycle, and nothing ever re-initializes it:
Reload()andNavigate()are then called on aCoreWebView2that no longer exists.This is the real cause behind #111 (closed as fixed in v75.0). v75.0 only restored the
startup scrape, so after a restart it looks fixed for a moment and then freezes again —
which matches the reporter's "everything is working fine now" right after updating.
Environment
d455399, clean checkout ofmaster, no local changes)ShowInlineUsageBars: true,UsageAutoRefreshSeconds: 120,UsageWindowOpened: false(inline bars only, tool window closed)Evidence (from a live session, 9 minutes after VS start)
LastUsageTimestampinclaudecode-settings.json--user-data-dir=…\ClaudeCodeExtension\WebView2EBWebView\lockfileSo the WebView2 is fully gone the moment
frame.Hide()runs at the end ofEnsureUsageToolWindowAsync's background-init branch: VS removes the tool window contentfrom the visual tree, the
HwndHostdestroys the hosting window, andCoreWebView2dieswith it.
Root cause
Controls/ClaudeCodeControl.Usage.cs:280short-circuits every refresh:Two things combine:
IsWebViewInitialized => _initialized(UI/ClaudeUsageControl.xaml.cs:651) is only an"OnLoaded has run once" flag, not a liveness check. It stays
trueforever, long afterCoreWebView2has been destroyed.Reload()(UI/ClaudeUsageControl.xaml.cs:919) istry { WebView?.CoreWebView2?.Reload(); } catch { }— with a deadCoreWebView2nothinghappens and nothing is reported.
Result: the working fallback (
ShowNoActivate→ scrape →Hide, line 295 ff.) is neverentered again after the first startup cycle. The same no-op also swallows the control's own
auto-refresh timer (
RestartAutoRefreshTimer→Tick += Reload,UI/ClaudeUsageControl.xaml.cs:909), which is why the Auto-refresh setting makes nodifference either.
Regression
Commit
27bf92a— "10.31 - Usage tab no longer blinks during background refresh". Beforethat, every refresh did the full show/hide cycle, which still works today (it produces the
one startup value). It was replaced by a hidden
Reload()with the assumption in thecomment that "CoreWebView2 processes navigation and JS messaging even when the frame is
hidden" — true only as long as the WebView2 is still alive, which it isn't after
Hide().Repro
Claude Code provider, inline usage bars on, usage tool window closed, auto-refresh on (120 s).
Start VS, wait for the bars to appear (startup scrape works).
Wait 5–10 minutes, then look at
LastUsageTimestampin%LOCALAPPDATA%\ClaudeCodeExtension\claudecode-settings.json— it never advances.Get-CimInstance Win32_Process -Filter "Name='msedgewebview2.exe'"shows no process withthe extension's
--user-data-dir.Now open the Claude Usage tool window → blank panel (confirmed).
OnLoadedbails outon
if (_initialized) return;soInitializeWebViewAsync()never runs again,OnWindowBecameVisiblenavigates anullCoreWebView2, andLoadingTextwas alreadyset to
Collapsedby the firstNavigationCompleted— hence not even a loading hint.Workaround for users until this is fixed: toggle the Show Usage toolbar button off and on.
ForceClose()destroys the tool window, so the nextFindToolWindow(create: true)builds afresh
ClaudeUsageControlwith a working WebView2.Suggested fix
IsWebViewInitializedgate with a real liveness check(
WebView?.CoreWebView2 != null) and only take the reload short-circuit when it holds.CoreWebView2is dead, run the startup show/hide cycle again including re-init.Since the blank-window symptom shows the existing
WebView2element cannot be revivedafter being torn out of the visual tree, this needs a fresh
WebView2instance placedinto a host container (rather than the fixed
x:Name="WebView"element), plus resetting_initializedsoInitializeWebViewAsync()runs again. That also fixes the blank panelon explicit open.
Reload()returnboolso the host notices a failed reload immediately, and force afull cycle on the next tick when the scrape wait times out (self-healing).
fallback path now uses
ShowNoActivate()instead ofShow().