Skip to content

Desktop app: main-window WebView2 renderer self-aborts with STATUS_BREAKPOINT (0x80000003); window goes blank until manually refreshed #4492

Description

@bghgary

Moved to github/app#3200. This is a desktop-app bug and this repo tracks the CLI. The re-filed issue has the corrected analysis; closing this one.

[Filed by Copilot on behalf of @bghgary]

The Copilot desktop app's main-window WebView2 renderer terminates itself with STATUS_BREAKPOINT (0x80000003). The window goes blank and stays blank until manually refreshed, losing any open canvas panel. 23 confirmed occurrences on one machine between 2026-08-13 and 2026-08-24, at roughly one per 3.9 working-hours of app uptime.

Supporting evidence — disassembly, full stack, crash-key table, A/B measurements — is in this gist, to keep this readable.

Root cause

A CHECK in Blink's accessibility tree serializer fires during the post-layout phase of a main frame — third_party/blink/renderer/modules/accessibility/ax_block_flow_iterator.cc:184, in AXBlockFlowData::ComputeNeighborOnLine:

case FragmentItem::kText:
case FragmentItem::kGeneratedText:
  if (!it->GetLayoutObject()) [[unlikely]] {
    // A generated text fragment item may not have a backing LayoutObject.
    // For regular text, we expect them to always have one.
    CHECK_EQ(it->Type(), FragmentItem::kGeneratedText);   // <-- fires
  }

A fragment item of type kText is reached with a null LayoutObject. The code assumes only generated text can lack one, and Chromium's own TODO three lines below notes the area is not fully understood: "Investigate when an item can be text, but its LayoutObject is not marked as such."

Abridged stack:

<int3>                                           <- the CHECK
blink::AXBlockFlowIterator::PreviousOnLineAsIndex
blink::AXInlineTextBox::NeighboringOnLineWithAXBlockFlowIterator
blink::AXObject::SerializeLineAttributes
blink::AXObject::SerializeInlineTextBox
ui::AXTreeSerializer<...>::SerializeChangedNodes   (recursion x8)
blink::AXObjectCacheImpl::SerializeAXUpdatesIfNeeded
blink::LocalFrameView::RunAccessibilitySteps
blink::LocalFrameView::RunPostLifecycleSteps
cc::ProxyMain::BeginMainFrame

Identified from disassembly rather than guessed, and identical across all 9 dumps on one runtime build. Nothing is logged at the failure: the CHECK compiles to IMMEDIATE_CRASH() with no call to a check-failure handler, so --enable-logging --v=1 across five deaths produced zero Check failed lines. There is nothing to capture.

The failure has survived five app versions (1.1.8 → 1.1.12) and four runtime builds (.78.101). Each build repeats its own crash offset exactly.

Why accessibility is on at all

No assistive technology is running on this machine (no Narrator/NVDA/JAWS). Accessibility is on because the app turns it on itself: github.exe embeds, in its Tauri configuration, "additionalBrowserArgs": "--disable-features=… --force-renderer-accessibility". That switch is unconditional, is byte-identical in 1.1.12, and selects kAXModeComplete — which includes kInlineTextBoxes, the mode bit the failing path requires. It appears on the WebView2 browser process command line, not the renderer's.

Ruled out

Not time-driven (lifetime at death 4.5–1278 min; inter-arrival CoV 0.85–0.90 against an exponential's 1.0, so the hazard is constant). Not memory (266–534 MB at death, while a healthy renderer peaked at 1435 MB and survived). Not back/forward cache (tested with the feature disabled and verified active: two deaths in 126 min at the same offset).

Activity-dependent. Across a full weekend of continuous uptime, zero deaths in 20 working-hours against 7.1 expected (P = 0.0008). All confirmed deaths fall between 08:00 and 17:59 local.

Workaround (verified)

Launching the app with WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--disable-renderer-accessibility, set for that process only: 16.7 working-hours with zero deaths, against a baseline of one per 3.9 (P = 0.014). One launch that accidentally bypassed it died in 1.35 hours with the original crash signature.

Two caveats. Set it process-scoped, not at User scope — at User scope it reaches every WebView2 host and degraded another app here. And it disables renderer accessibility outright, so it is unsuitable for anyone using assistive technology. It is a stopgap, not a fix.

Two arms that do not work:

  • --force-renderer-accessibility=basic / =form-controls — environment-variable arguments land before the app's own, and Chromium takes the last occurrence of a switch, so the app's bare --force-renderer-accessibility overrides them. --disable-renderer-accessibility is a different switch and wins in either order.
  • --disable-features=AccessibilityBlockFlowIteratorrelocates the crash rather than removing it. The work reroutes to the sibling AXInlineTextBox::NeighboringOnLine, which has no CHECK, so the renderer dies as a 0xc0000005 null dereference instead. Over 4.3 working-hours the crash rate was not distinguishable from baseline (2 deaths, p = 0.30) — an earlier version of this text said "worse than stock", which counted only a 14-minute cluster and discarded the 3.85 clean hours before it.

What would help

The CHECK itself is a Blink bug and no public Chromium issue appears to exist for it. Two things are in this app's hands:

  1. Reconsider hard-coding --force-renderer-accessibility. It is what puts this machine on the failing path with no assistive technology running. Enabling accessibility on demand would avoid the crash entirely for users who need no accessibility tree, without taking it from those who do.
  2. Recover the view when RenderProcessGone fires. The app logs nothing at the failure and can leave the window blank for up to 29m44s, while WebView2's own Breadcrumbs records the event.

Separately, and unrelated to this crash: a session can be created without ever receiving its permission grants, and is then degraded for its whole life. Across 186 sessions, the 6 that received zero session.permissions_changed events are exactly the 6 that raise permission prompts, each burning the full 600 s timeout; in healthy sessions grants arrive at a median of 0.02 s, so this is not slow delivery — it never happens and is never retried. Details in the gist.

Minidumps for 24 captured deaths are retained and available on request.

Environment

  • Copilot desktop app github.exe 1.1.8 – 1.1.12; Copilot CLI 1.0.79
  • WebView2 runtime 151.0.4129.78 → .86 → .93 → .101 (deaths observed on all four) → .107 (in use only since the workaround, so no data; it still ships ax_block_flow_iterator.cc)
  • Windows 11 build 26310, 63.6 GB RAM

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions