VSCode extension v2.1.141: "Unhandled case: [object Object]" banner from assertNever on unknown streaming-event types
Summary
webview/index.js in the VSCode extension has a TypeScript assertNever-style exhaustive-check (helper QB1) over Anthropic streaming-event types. When the server emits an event type the switch doesn't cover, the helper throws Error(\Unhandled case: ${J}\) interpolating the whole event object instead of its .type field — producing the user-visible banner text:
Unhandled case: [object Object]
View output logs · Troubleshooting resources
The banner is non-dismissable except via the manual ×, persists across subsequent tool calls, and is alarming to operators who reasonably assume the session is broken. The underlying session and the model are unaffected — it's a renderer-side throw, not a data-flow problem.
Environment
- Extension:
anthropic.claude-codev2.1.141 - Platform: darwin-arm64 (macOS)
- Bundle path:
~/.vscode/extensions/anthropic.claude-code-2.1.141-darwin-arm64/webview/index.js
Source of the throw
// Helper (assertNever pattern, emitted by bundler)
function QB1($, Z) { throw Error(Z ?? `Unhandled case: ${$}`) }
// Caller — switch over streaming-event types
switch (J.type) {
case "signature_delta": { /* ... */ break }
case "compaction_delta": break
// ...other cases...
default: QB1(J) // ← passes whole event object, not J.type
}
The template-string interpolation ${$} calls .toString() on the event object, yielding the literal "[object Object]".
Known-handled cases in v2.1.141
Extracted from the bundle (last ~3 KB before the Unhandled case literal):
text, text_delta, thinking_delta, signature_delta, citations_delta, input_json_delta, compaction_delta, content_block_start, content_block_delta, content_block_stop, message_start, message_delta, message_stop.
Likely missing cases
ping— Anthropic streaming keepalive eventerror— server-side error events- Any new content-block-delta type shipped after the v2.1.141 build cutover
A real reproduction of the offending event requires capturing the VSCode extension output log at the moment of the throw — I have not done that here. The diagnosis is grounded in static source inspection.
Suggested fixes
1. Format the error usefully (1-line change):
function QB1($, Z) {
throw Error(Z ?? `Unhandled case: ${$?.type ?? JSON.stringify($)}`)
}
Operators would then see e.g. Unhandled case: ping instead of Unhandled case: [object Object], making the bug self-reporting.
2. Soften the failure mode (recommended in addition to #1):
Streaming events the renderer doesn't recognize should be logged to the output channel and skipped, not thrown as a top-of-window banner. Hard-throwing on every new server-side event type couples extension stability to backend compatibility — every Anthropic API addition risks a banner storm in older extension versions still in the field.
Repro
The banner fires intermittently during long-running sessions with many streaming responses. I have not produced a deterministic repro (would need to inject a streaming event of an unknown type).
Diagnosis trail (for reproducibility)
# Confirm extension version
ls ~/.vscode/extensions/ | grep claude-code
# Find the throwing helper
grep -n "Unhandled case" ~/.vscode/extensions/anthropic.claude-code-2.1.141-*/webview/index.js
# Enumerate the switch's known cases
python3 -c "
import re
data = open('$HOME/.vscode/extensions/anthropic.claude-code-2.1.141-darwin-arm64/webview/index.js').read()
m = re.search(r'Unhandled case', data)
chunk = data[max(0, m.start()-3000):m.start()]
print(sorted(set(re.findall(r'case\"([^\"]+)\"', chunk))))
"
Note on a red herring
In a prior internal investigation we briefly hypothesized that the banner was triggered by the harness's <persisted-output> wrapper (the marker shown when a tool result is spilled to disk because it exceeds the inline display cap). That hypothesis was falsified: the string persisted-output does not appear anywhere in the extension bundle — the wrapper is harness-side text the extension just renders as a string, and is unrelated to the streaming-event switch.
21 Comments
Is there a workaround for this? It's happening on my Windows 11 Business dev machine as well.
I'm also experiencing it on Windows 11 Business. You guys on Windows can run this command to patch it for now
Patch 1 — readable banner text:
Patch 2 — suppress inner content-block-delta banner:
Patch 3 — suppress outer streaming-message banner:
Confirming on macOS-arm64, VSCode-Insiders, extension v2.1.141 (Anthropic publisher build, not a fork). Hit this today (2026-05-13 20:22 PST) mid-session against
claude-opus-4-7[1m]via Anthropic direct API (not Bedrock), so it's not Bedrock-specific despite #52151's witness.Two distinct call sites
QB1is reached from two switch statements inwebview/index.js, both worth fixing:The first one is the wider concern: standard Anthropic streaming routinely emits
pingkeepalives anderrorevents, neither of which has a case here. A singlepingfrom the API throws this banner.macOS/bash patch (counterpart to the PowerShell one upstream)
This is more aggressive than the PowerShell snippet above (which only formats the error usefully but still throws). For long-running sessions where a single unknown event would otherwise tear down the UI mid-stream, warn-and-continue restores resilience. Tradeoff: future genuinely-malformed events become silent — but they'll be visible in the Output → "Claude VSCode" log as a
[warn], which is the correct severity for "renderer doesn't know about this event type yet."Suggested doctrine for the official fix
The exhaustive-check pattern is great for catching API drift in dev, but in shipped extensions it should fail soft (log + skip), because the extension lifecycle is much longer than the API lifecycle. Every new server-side event type would otherwise cause a banner storm in older extensions still installed in the field. Suggest both:
CLAUDE_CODE_STRICT_SSE=1) that restores the throw for SDK developers who want to catch protocol drift early.Confirming this reproduces on win32-x64 as well (the issue only documents darwin-arm64), and as a real user-facing occurrence — not static analysis only.
anthropic.claude-codev2.1.141 (win32-x64)~/.vscode/extensions/anthropic.claude-code-2.1.141-win32-x64/webview/index.jsIndependent static inspection of the win32 bundle matches your findings exactly:
function QB1($,Z){throw Error(Z??Unhandled case: ${$})}switch(J.type){ ... default:QB1(J) }— the whole event object is passed, notJ.typetext_delta,citations_delta,input_json_delta,thinking_delta,signature_delta,compaction_delta→ thendefault:QB1(J)So the bug is platform-independent (shared webview bundle, identical code on win32 and darwin) and does fire in normal use, not just in theory.
+1 on both suggested fixes — especially #2 (log-and-skip instead of hard-throw): the throw currently aborts rendering of the in-flight streamed message in that tab, so a single unrecognized event type silently loses the assistant's reply for that turn.
Adding a Windows data point + an additional trigger path that confirms your diagnosis.
Environment
anthropic.claude-codev2.1.141win32-x64Windows 11~/.vscode/extensions/anthropic.claude-code-2.1.141-win32-x64/webview/index.jsBanner reproduces identically on Windows. Issue is platform-independent (as expected from your static analysis of the renderer bundle).
Additional trigger path (hook-output side)
In our project we have ~30 user-defined hooks (
UserPromptSubmit,SessionStart, etc) that read append-only JSONL log files. When one of those JSONL files is left in aUU(merge conflict unresolved) state, the hook'sjson.loads(line)crashes on the conflict marker lines, the hook returns a malformed payload, and the banner fires.This is consistent with your hypothesis that the throw catches event types the switch doesn't cover — in our case the unknown "event" likely originates from hook-output parsing on the extension side, not from a streaming-event missed type. Either way the renderer-side fix (#1 in your suggested fixes) catches both classes:
would have shown us
Unhandled case: <hook event type>or the stringified payload — instant diagnosis instead of 30+ minutes of log archaeology.Workaround we adopted (until extension fix lands)
Two-layer defense on the userland side:
.gitattributeswithmerge=unionfor append-only JSONL files so concurrent appends from different branches intercalate automatically instead of producing conflict markers.This eliminates the upstream condition that fed malformed payloads to the extension. It does not address the renderer-side fragility — your suggested fixes (#1 useful error message + #2 log-and-skip unknown events) remain the proper resolution.
Happy to provide an extension output log if a deterministic repro becomes useful — would need guidance on what level to capture (the user-facing "View output logs" doesn't show the throw site).
+1 reproducing on darwin-arm64, VSCode stable, extension v2.1.141.672, Opus 4.7 1M via direct Anthropic API (matches OP's environment). Two data points that don't appear upthread:
1. Trigger correlation: a 33s stream stall immediately precedes the throw
Relevant slice of the Output → "Claude VSCode" log (timestamps in UTC, banner appeared shortly after \
14:11:20\local):\
\\\14:10:09.980 [WARN] Streaming completed with 1 stall(s), total stall time: 33.0s
14:10:09.982 [DEBUG] Fast mode unavailable: Fast mode requires extra usage billing
14:11:20.474 Received message from webview: open_output_panel ← user clicked \"View output logs\" from the banner
\
\So the timeline is streaming finishes with a long stall → ~70s of idle → banner. Not \"mid-stream\", not \"on stream start\" — after a stall-recovered completion. Worth checking whether stall-recovery emits a synthetic event (or a delayed \
ping\/\error\) that the outer \XB1.processStreamEvent\switch doesn't cover.2. ~~Backend kept executing tool calls after the banner — UI throw did NOT abort the session~~ → Retracted, see edit above
~~Contrary to the symptoms from @JCBauza (\"tear down the UI mid-stream\") and @TshyGO (\"silently loses the assistant's reply for that turn\"), in my case the agent kept working while the banner was up.~~ Correction: the main stream was lost the same way they describe. The tool-dispatch lines I saw after the banner —
\
\\\14:11:23 tool_dispatch_start tool=Write ...
14:11:29 tool_dispatch_start tool=TodoWrite ...
14:11:29 [API REQUEST] /v1/messages ...
\
\— were emitted by parallel \
Task\subagents running their own event loops, not by the main session that hit the throw. So my data point becomes the narrower (but still useful) one: the throw is scoped to one session's renderer, not the host process — sibling subagent loops in the same window are unaffected. The main stream behaves exactly as described upthread.3. Not hook-output related
Excluding @arthur-mayor's hook-output trigger path for this repro: \
[DEBUG] Hooks: Found 0 total hooks in registry\— no user-defined hooks configured in this project, and the banner still fires. Confirms there are multiple independent trigger paths into \QB1\.Quick env summary
| Field | Value |
|---|---|
| OS | macOS Darwin 25.3.0 (arm64) |
| Extension | \
anthropic.claude-code\v2.1.141.672 || Entrypoint | \
claude-vscode\(per \x-anthropic-billing-header\) || Model | \
claude-opus-4-7[1m]\|| Backend | Anthropic direct API |
| Hooks | 0 |
| MCPs active | none (only cached \
claude.ai Google Drive\skipped on needs-auth) || Context | \
tokens=[REDACTED] level=ok effectiveWindow=980000\(well below limit) || Concurrency | parallel \
Task\subagents active at the time of throw |Adding another macOS arm64 data point plus a behavioral note + objective hot-patch verification, after independently reproducing @JCBauza and @carlosresu's analysis.
Environment
anthropic.claude-codev2.1.141 (darwin-arm64), Anthropic publisher buildIndependent reverse-engineering confirms the call-site layout
Same
QB1($, Z) { throw Error(Z ?? \Unhandled case: ${$}\) }definition, same twodefault:QB1(...)call sites at byte offsets 3144077 (outerprocessStreamEventswitch over$.type) and 3145478 (innercontent_block_deltaswitch overJ.type) inwebview/index.js. TheQB1function definition itself sits at byte 3145777 — a third occurrence — which the patches below leave intact so any legitimate throw site outside the two default arms still works.Behavioral observation (didn't see this in the thread yet)
Manual "resume" of a stalled session is itself susceptible: the new stream proceeds fine until the next unrecognized event/delta type lands, at which point the banner fires again and the stream halts. So a single long task with multiple thinking/tool segments can hit the banner 3–5 times in succession, each "resume" advancing a bit further before hitting the next unhandled variant. This makes the bug feel non-deterministic ("sometimes it works"), but the pattern is consistent — it's just that each task has a different distribution of recognized vs. unrecognized event types arriving.
macOS arm64 hot-patch (Patch-2 equivalent of @carlosresu's PowerShell)
Then VSCode
Developer: Reload Window.Objective verification (so anyone replicating can sanity-check)
| Check | Pre-patch | Post-patch |
|---|---|---|
| SHA256 (first 16) |
d756d1d369cfb41a|b46413d05cf855d2|| File size (bytes) | 4,798,391 | 4,798,389 |
|
default:QB1(occurrences | 2 | 0 ||
default:breakoccurrences | 4 | 6 ||
QB1function definition at 3145777 | present | preserved |File-size delta is -2 bytes (
QB1($)6 chars →break5 chars × 2 sites).Banner has not recurred across either of my two VSCode windows since reload (~2 hours of mixed cc-ops and other-project usage). Will report back if anything regresses before 2.1.142+ ships with the proper switch enumeration.
Thanks for the canonical write-up @JCBauza and the workaround template @carlosresu — the byte-offset detail saved me a lot of grepping.
Additional repro:
Readtooloffsetpassed as array triggers thisFiling from #59101 (closing as duplicate of this).
We identified a specific trigger for this crash: the model occasionally generates a
Readtool call withoffsetas an array (e.g.,[1500]) instead of a plain number (1500). The tool input validator catches it, but the extension UI then hits the unhandled case instead of rendering a graceful error.Log evidence (
exthost/Anthropic.claude-code/Claude VSCode.log):Occurred twice in a single session within ~5 minutes. Each time it was immediately followed by the "Unhandled case: [object Object]" banner and session freeze.
So there are at least two distinct triggers for the same UI crash:
Both suggest the
assertNever/ unhandled-case path in the renderer needs a catch-all fallback.I'm having the same issue with the unknown unhandled case. For me, I believe it started when I last updated VS Code this week. Did anyone else have this issue inside of VS Code and get this after the update from VS Code?
Personally the disruption caused by whatever the unhandled case is more painful to me, but knowing what Claude thinks the issue is would also be useful.
Inspired by the other person in this thread who created a patch for this issue, I had Claude Code create one for me, too, that would be a bit more informative of what was going on so I could address the actual issue underneath. So here's the pseudocode my Claude created as a workaround. Maybe you can get your Claude to help you to understand the root cause of the error on your machine as well, until Anthropic has an actual fix.
How to apply (on windows 11)
Transcribe the pseudocode replacement below into a PowerShell one-liner
similar to the patches already in this thread, with these requirements:
Set-Content -NoNewline -Encoding UTF8(PS 5.x defaults can writeUTF-16/BOM and corrupt the JS file)
before reopening
Diagnostic patch for
[object Object]banner — single-point variantA consolidated alternative to the existing patches in this thread. Modifies only the
QB1helper function inwebview/index.js, so both calling sites (the inner content-block-delta switch and the outer streaming-message switch) automatically benefit.What it does
event.typeinstead of literal[object Object].[claude-code:unhandled-event], live, filterable.localStorage— keyclaude_code_unhandled_events, rolling last 50 entries, survives webview reloads.This is especially useful for users hitting this banner from different triggers (the thread now confirms at least two: unknown SSE event types and tool-input validation errors). Capturing the actual event content lets users distinguish which class of failure they're seeing.
Pseudocode
How to inspect captured events
After the banner fires:
Then either:
[claude-code:unhandled-event](live entries during the session), or``
javascript
``JSON.parse(localStorage.getItem('claude_code_unhandled_events'))
Benefits vs other patches in this thread
| Approach | Banner visible? | Banner readable? | Forensic capture? | Modification points |
|---|---|---|---|---|
| Patch 1 (in-thread) | Yes | Yes (event type) | No | 1 (QB1) |
| Patches 2 + 3 (in-thread) | No | N/A | Console only, both switches | 2 (both switches) |
| This patch | Yes | Yes (event type) | Yes (console + localStorage, persistent) | 1 (QB1) |
Caveats (unchanged from other in-thread patches)
anthropic.claude-code-2.1.141-win32-x64). Re-apply after upgrade or downgrade.Set-Contentdefaults can corrupt the JS file by writing UTF-16 with BOM — always pass-Encoding UTF8.@The-best-of-us
I think this issue has been resolved in the latest version 2.1.142. Have you tried upgrading?
V 2.1.142, bug still alive
Yep, seconding, I'm on 2.1.142 and still getting this.
also still getting this bug on 2.1.142
Still reproducing on v2.1.141 (build .672), macOS, VS Code extension.
The
Unhandled case: [object Object] · View output logs · Troubleshooting resourcestoast fired five times in one session today (2026-05-15), spanning roughly 90 minutes (09:12 → 10:43 PT). Local logs show each occurrence follows the same sequence:Stream started - received first chunk[WARN] [Stall] stream_idle_partial lastChunkAgeMs=15000 bytesTotal=N idleDeadlineMs=300000(warning repeats every 15s,bytesTotalstays flat)sdk_stream_ended_no_resultwithhad_error: trueUnhandled case: [object Object]toast — the actual error object is not surfacedTimestamps from today's log (all v2.1.141.672):
| Time (PT) |
had_error|message_count||---|---|---|
| 09:12:17 | true | 13 |
| 09:13:06 | true | 15 |
| 09:48:51 | true | 37 |
| 10:36:28 | true | 114 |
| 10:43:22 | true | 129 |
MCP servers configured:
claude.ai Rube,claude.ai Google Drive(both connect cleanly at startup;claude.ai Gmailandclaude.ai Google Calendarare skipped asneeds-auth). The Rube transport stays connected — no explicit MCP disconnect is logged before the toast — but the user-facing fallout matches the original report: the webview can't decode the underlying error object and falls back to the generic string.If the linked PR has shipped to build .672, I'm not seeing it. Happy to attach a redacted log slice around any of the five timestamps if that would help triage.
Seems to be working again with 2.1.143 on macOS for me so far.
+1, hitting this hard on Windows 11.
stream_idle_partialafter ~670 bytes, thensdk_stream_ended_no_result had_error: truecch=00000(zero cache hits —every retry reprocesses full context, expensive)
red banner, chat stops rendering
Tried: reload window, VPN off (never on), latest extension.
Nothing helps except starting a fresh session.
This is costing real money — partial streams still bill
input tokens and the cache misses compound it. Please
prioritize either (a) a retry button so we don't lose
the turn, or (b) suppressing billing when had_error=true
fires before any usable output.
Yes it's there from version 140 to 142. we confirmed 139 does not have it and we are not sure about 143 but 1ppl said it is ok. You definitely use the bugged version.
Just reporting I'm still getting it on Mac OS too for
2.1.143.Second repro on v2.1.143 (build .143). The render-miss class of this bug is still alive on 2.1.143 for me, not just the visible "Unhandled case" toast.
Environment
anthropic.claude-code-2.1.143(Linux remote via VS Code Remote-SSH from Windows 11 laptop)Spawning Claude with SDK query function ... version: 2.1.143 ... permission mode: bypassPermissionsclaude-opus-4-71M contextbypassPermissions(so not the auto-modetoAutoClassifierInputworkaround path mentioned in #58984)api.anthropic.comRepro this morning
Same shape as the session I described in #59054. User submitted a slash-skill invocation with ~3 KB prompt, model ran ~12 tool calls (
Bash,Read,mcp__servicenow__*,ToolSearch), produced a 5253-char final assistant text block, all written to JSONL on disk.GUI tab stayed truncated at the user prompt. Final text never appeared in the panel. Subsequent shorter assistant turns from outside the skill scope did render fine in the same session.
Stall signature on 2.1.143
The 45.8 s stall lands inside the final-text streaming window, ending ~28 s before the JSONL write. Same network/timing profile as my earlier repro in #59054 (74.5 s + 36.2 s stalls in the same turn).
Differentiator I haven't seen explicitly called out yet
In my session JSONLs, every assistant entry that fails to render in the webview has the field
attributionSkill: "<skill-name>"set. Every assistant entry that does render in the same session has noattributionSkillfield (because it was generated outside any slash-skill scope). Sample shape of the not-rendered entry:It is the only structural difference between the missing-from-GUI entry and the visible entries in the same session. I have not been able to verify whether the webview switch has a missing branch for skill-attributed assistant turns (the
webview/index.jsbundle on 2.1.143 still has theQB1assertNever pattern from the OP), but the correlation across two independent sessions on two different versions is exact.Other observation from 2.1.143 log
Stop hook validation failure fires at the same moment the missing assistant turn is persisted, schema rejecting
hookSpecificOutput.additionalContexton Stop events (only valid forPreToolUse/UserPromptSubmit/PostToolUse/PostToolBatch). Unlikely to be the cause of the render miss (the hook output is informational and the turn is already persisted by the time the validation runs), but the timing collision is consistent across both my sessions, in case it shares a code path with whatever wires the assistant turn into the webview.Asks for maintainers
webview/index.jsswitch table dump (case list) be made available so users can verify which streaming event types are exhaustively handled in the current build? That would let affected users self-diagnose whether their stalled-and-resumed event hit an unhandled case branch.Happy to attach full JSONL + extension log for both sessions privately.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.