Skip to content

[Bug]: Desktop Browser automation errors lose their type over IPC and report as 'failed on client' #15336

Description

@PixPMusic

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/desktop

Steps to reproduce

  1. In T3 Code Desktop, have an agent open a Browser tab (preview_open).
  2. Run preview_wait_for with text that never appears and a short timeoutMs, or run preview_click with a selector that matches nothing.

Expected behavior

The agent sees a typed failure: Preview automation waitFor timed out after …ms, or the invalid-selector / non-editable-target error, which carry specific guidance.

Actual behavior

The agent sees a generic Preview automation waitFor failed on client preview-… (or click failed on client …).

Automation operations that run in the desktop main process (apps/desktop/src/preview/Manager.ts: waitFor, click, type, …) fail with typed errors such as PreviewAutomationTimeoutError, PreviewAutomationInvalidSelectorError, and PreviewAutomationTargetNotEditableError. Those failures reach the renderer through ipcRenderer.invoke. Electron rejects invoke with a plain Error carrying only a message, so _tag and the other fields are lost. The renderer then wraps the error in PreviewAutomationOperationError.fromCause (apps/web/src/components/preview/previewAutomationErrors.ts). It no longer matches a known host error, so it's reported to the server as PreviewAutomationExecutionError. The server's classifyResponseError already handles PreviewAutomationTimeoutError, PreviewAutomationInvalidSelectorError, and PreviewAutomationTargetNotEditableError, but those tags never arrive from desktop-side operations.

Until now, a wait_for timeout usually still read "timed out" by accident: the server's own deadline fired first and produced the timeout error (see #15335). With that race fixed, the host's real answer arrives and shows up as "failed on client".

Impact

Minor bug or occasional failure

Version or commit

main @ fffe6e6

Environment

macOS 26, T3 Code Desktop dev build and Nightly 0.0.46-nightly.20261003.2632; Codex provider

Logs or stack traces

# dev server trace, preview_wait_for with timeoutMs 2000 that did not match
PreviewToolkit.invoke  Failure  PreviewAutomationExecutionError: Preview automation waitFor failed on client preview-c94c30600ad9bb3affc165451e0feb7f.

Activity

  1. juliusmarminge commented on Oct 3, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks for tracking this down, @PixPMusic! I can confirm it. Desktop preview automation builds typed failures in apps/desktop/src/preview/Manager.ts (PreviewAutomationTimeoutError, PreviewAutomationInvalidSelectorError, PreviewAutomationTargetNotEditableError, and others), and apps/desktop/src/ipc/DesktopIpc.ts returns them by rejecting the ipcMain.handle promise. Electron's ipcRenderer.invoke keeps only the Error message (see electron#24427), so _tag and the other fields never reach the renderer.

    PreviewAutomationOperationError.fromCause in apps/web/src/components/preview/previewAutomationErrors.ts only keeps a value that's already a host error, or a plain object whose _tag is PreviewAutomationTargetNotEditableError. A thrown desktop error matches neither, so it becomes PreviewAutomationOperationError, whose responseTag is PreviewAutomationExecutionError. serializePreviewAutomationHostError also drops cause, so the desktop message is gone before it reaches the wire. classifyResponseError in apps/server/src/mcp/PreviewAutomationBroker.ts handles the timeout, invalid-selector, and not-editable tags, but the desktop host never sends them. It falls through to the default branch, which is the trace you pasted.

    Two details affect the fix, though not the diagnosis:

    • Keeping _tag across IPC wouldn't be enough for timeout or invalid selector. Those desktop classes aren't in the host-error union, and fromCause doesn't match them by shape. Only the not-editable case is rehydrated, and its test passes a plain object rather than an Electron rejection.
    • preview_click with a selector that matches nothing raises PreviewAutomationTargetNotFoundError. There's no public tag for that, or for coordinates outside the viewport, so those would stay PreviewAutomationExecutionError even once the existing tags survive IPC.

    On current main, an unmatched preview_wait_for usually doesn't show this yet. The broker's Effect.timeoutOption(timeoutMs) fires first, returns the "timed out" error, and evicts the host (the open #12898, also reported as #15335, with an open fix attempt in #12899). Immediate desktop failures like an invalid selector or a non-editable target already come back as the generic client error today.

    Likely fix area

    • One option is for the desktop side to return a serializable failure payload (tag plus fields) over IPC, and for the renderer to map it onto the response tags the broker already classifies.
    • Adding public tags for target-not-found and out-of-viewport could be considered separately.

    A maintainer will decide on the fix direction.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 3, 2026
  3. PixPMusic commented on Oct 3, 2026

    @PixPMusic
    ContributorAuthor

    Cross-linking related work: #3714 is the click target-not-found case of this same IPC loss, and #7301 sends a typed click outcome across IPC for that path. This issue covers the desktop tags that already have public counterparts (waitFor timeouts, invalid selectors, non-editable targets). Once #12899 lands, unmatched preview_wait_for calls will start hitting it too: on a real desktop the host's timeout reply arrives and reads waitFor failed on client … instead of timed out (observed on two Macs; details on #12899).

  4. juliusmarminge commented on Oct 7, 2026

    @juliusmarminge
    Member

    Obsolete since #15328: preview automation no longer runs in the desktop app, so errors no longer cross IPC. The server host returns typed errors directly.

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

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions