Skip to content

[Cowork UI] "Unhandled case: [object Object]" banner — MCP tool-result envelope + SDK stream-end-with-error renderer paths #59139

Closed as not planned

Description

@hnatloz

Cowork UI bug report — Unhandled case: [object Object]

Summary

The Claude desktop application's Cowork mode renders an error banner with the
literal text Unhandled case: [object Object] when a tool returns a successful
MCP CallToolResult whose content is a JSON-serialized object containing an
error key (instead of a proper JSON-RPC error response).

The banner appears at the top of the chat with a red bar. Closing and
re-opening the session usually clears it.

Environment

  • Claude desktop version: 1.7196.0.0 (x64)
  • OS: Windows 11 (64-bit)
  • Cowork mode (research preview)
  • Date observed: 2026-05-14

Reproduction

  1. Configure a stdio MCP server whose tool handler catches all exceptions and,
    on failure, returns a successful result containing JSON of the form:
    return [types.TextContent(type='text',
        text=json.dumps({'error': str(e), 'tool': name}))]
  2. Call the tool in a way that triggers the exception branch.
  3. The Cowork UI displays a red banner:
    Unhandled case: [object Object]
  4. The actual error JSON is correctly delivered to the assistant turn — only
    the UI banner is broken.

A minimal reproducer is the case where an MCP wrapper passes an unsubstituted
"${BW_SESSION}" placeholder into the spawned server's environment, but any
"valid result, contains 'error' key" pattern triggers the same UI path.

Expected behavior

The UI should either:

  • Render the error JSON as text (with formatting), or
  • Recognize the { error: ... } shape and surface it like a JSON-RPC error
    response (showing error.message).

Observed (incorrect) behavior

The UI's error renderer falls into a default switch branch and stringifies
the JS error object with the implicit toString(), producing the literal
[object Object]. This makes the banner uninformative and the underlying
problem invisible without inspecting View output logs.

Workaround at the MCP-server side

In our plugin we replaced

except Exception as e:
    return _ok({'error': str(e), 'tool': name})

with

except Exception:
    logger.exception(f"Tool error: {name}")
    raise   # let the mcp framework convert to JSON-RPC error

Once tools raise instead of wrapping the error in a "successful" envelope,
the Cowork UI renders errors correctly (Error.message in the banner).

Suggested fix

In the Cowork client's tool-result renderer, the default branch of the
switch handling tool result content should either:

  1. Pretty-print the unknown object (JSON.stringify(value, null, 2)), or
  2. Detect the common { error: string, tool?: string } envelope shape and
    render it as an error.

Either change avoids the [object Object] artifact without requiring any
server-side change.

Context / chat reference

This issue was reproducible reliably in our cegcsoport-mail MCP plugin
during a debugging session on 2026-05-14, before we patched the server to
re-raise exceptions. The chat session id where it occurred was
eb87764f-d944-42bd-9b4a-ce0e79aa6fdc (recorded by the VSCode extension's
session log at 17:34 local time).


Update — Second code path (added 2026-05-14 18:41)

After patching all of our MCP plugins (so they raise instead of returning
_ok({'error': ...}) envelopes) and adding a defensive
_ensure_no_error_envelope decorator, the [object Object] banner
still appears, especially under parallel sessions. The actual trigger
is now demonstrably NOT an MCP tool-result envelope — it is a
stream-end-with-error event in the Claude Code SDK that the Cowork UI
renderer also fails to handle.

Observed log sequence (VS Code Claude extension,

cc_version=2.1.141.055, cc_entrypoint=claude-vscode)

Session id: 2c87ceb4-9eed-41af-a6ba-4d163bdb04a7, title /start.

Occurrence 1, 18:36:

18:36:38  /start indul (agent:builtin:Plan)
18:36:44  Streaming stall detected: 84.5s gap between events
18:36:45  bitwarden MCP server connected after 18.9s (slow!)
18:36:54  Streaming completed with 1 stall(s), total stall time: 84.5s
18:36:54  Second API request created (source=sdk, retry)
18:37:03  sdk_stream_ended_no_result {"had_error":true,
                                      "subagent_count":0,
                                      "message_count":146}
18:37:14  Stream re-starts
18:37:26  AskUserQuestion permission requested
[user sees `Unhandled case: [object Object]` banner]

Occurrence 2, 18:40 (same session, ~3 minutes later):

18:40:15  Stream idle 15s (0 bytes received)
18:40:26  Slow first byte: no stream chunk 30.0s after request sent (attempt 1)
18:40:27  Stream finally starts
18:40:42  Stream idle 15s (701 bytes)
18:40:45  Write tool validation error: "File has been modified since read,
            either by the user or by a linter. Read it again before
            attempting to write it."
18:40:50  Read tool dispatched (success)
18:40:57  Stream idle 30s
18:40:57  sdk_stream_ended_no_result {"had_error":true,
                                      "subagent_count":0,
                                      "message_count":155}
18:41:04  Streaming stall 36.6s gap
[user sees `Unhandled case: [object Object]` banner]

Pattern (both occurrences)

  1. The Claude API responds slowly (30+ second first-byte / idle periods).
  2. An SDK-level error happens (in occurrence 2 it was a
    Write tool validation error: File has been modified since read).
  3. The SDK aborts the current stream and emits the webview event
    sdk_stream_ended_no_result { had_error: true, ... }.
  4. The Cowork renderer receives an Error object (or similar) it does not
    handle and falls into the same default switch branch as before,
    stringifying it as [object Object].

Why our MCP-server fixes do not solve this case

We verified by source audit that all of our MCP plugins are now clean
of _ok({'error': ...}) envelopes (cegcsoport-mail, cegcsoport-cpanel,
pg-cegcsoport-ro, helpgoweb, plus _ensure_no_error_envelope
decorator on the two large servers). The trigger has moved upstream into
the SDK/UI layer.

Aggravating factor: parallel sessions

The bug fires noticeably more often with 3–4 parallel sessions running.
Likely contributors:

  • shared file-system state → more Write tool validation error cases
  • competing MCP server startups → longer initial stalls
  • multiple long-lived streams → more occasions for stream-end-with-error.

Updated suggested fix

In addition to the original suggestion (handle { error: string } in the
tool-result renderer), the renderer that processes
sdk_stream_ended_no_result { had_error: true, ... } should also:

  1. Inspect eventData.error (or whatever it carries) and surface
    error.message instead of relying on implicit toString().
  2. Provide a more informative banner — at minimum the
    error.constructor.name and message, optionally the request id
    (x-client-request-id) so users can correlate with their output logs.

A defensive JSON.stringify(value, getCircularReplacer(), 2) would also be
strictly better than the current [object Object] artifact, even if the
shape isn't recognised.

Workarounds that we found practical

  • After the banner appears, closing and reopening the session usually
    recovers it.
  • Reducing the number of simultaneously running parallel sessions reduces
    the frequency.
  • Avoiding the bare /start slash command (multiple matching skills) and
    using fully-qualified names like /productivity:start reduces ambiguity
    during the slow-startup window.

Activity

  1. github-actions commented on May 14, 2026

    @github-actions

    Found 3 possible duplicate issues:

    1. Unhandled case: [object Object] on stream end with had_error: true after long sessions #59013
    2. UI shows "Unhandled case: [object Object]" toast when SDK stream ends with error after stall #59054
    3. VS Code extension: "Unhandled case: [object Object]" when the API stream stalls (CLI/TUI mode unaffected) #59067

    This issue will be automatically closed as a duplicate in 3 days.

    • If your issue is a duplicate, please close it and 👍 the existing issue instead
    • To prevent auto-closure, add a comment or 👎 this comment

    🤖 Generated with Claude Code

  2. Machiavello7 commented on May 15, 2026

    @Machiavello7

    Adding empirical isolation data from a related case (just closed #59374 as duplicate):

    Test that isolates the bug to the extension (not CLI/SDK): on the same Windows 11 machine where the VS Code extension was reproducing the stall, running claude --print "say hi" from a separate Windows Terminal (not VS Code's integrated terminal) returned cleanly. Same claude binary v2.1.142, same network, same auth, same MCP servers. Only difference: bypassed the extension entirely.

    This rules out network, system, antivirus, or SDK-level issues — the defect is in the extension's stream handling.

    Additional pattern observed: failures correlate with MCP server reconnect events (consistent with #59307). The system reminders showing repeated MCP disconnect/reconnect cycles immediately precede the Unhandled case: [object Object] overlay.

    Trigger threshold seems payload-driven: the same machine and config that fails in the extension is reproducible only when the initial payload is heavy (5 MCP servers + large CLAUDE.md + auto-loaded memory entries). Sibling machines on the same network with lighter payloads (1-2 MCPs) do not reproduce.

    Workaround for affected users: use the standalone CLI (claude interactive or claude --print) from a separate terminal. Same auth, same MCPs (when run from a directory with .mcp.json), no stalls.

    Full reproducibility detail, environment specs, and Output panel log capture in #59374 (closed as duplicate).

  3. github-actions commented on Jun 15, 2026

    @github-actions

    Closing for now — inactive for too long. Please open a new issue if this is still relevant.

  4. github-actions commented on Aug 17, 2026

    @github-actions

    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.

  5. locked as resolved and limited conversation to collaborators on Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:coworkarea:mcpbugSomething isn't workingduplicateThis issue or pull request already existsplatform:vscodeIssue specifically occurs in VS Codeplatform:windowsIssue specifically occurs on WindowsstaleIssue is inactive

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions