Bug: user-agent shadow roots are reported as SHADOW(open) in the serialized page state

Author: nina-mirCreated Sep 15, 2026Updated Sep 15, 2026
Labelsbug

Browser Use Version

d8110c5ff87ccba887aaa726cdb780f2f84bef8d

Bug Description, Steps to Reproduce, Screenshots

CDP defines three shadow root types — user-agent, open, closed — but the code tests only for closed and treats everything else as open. Chrome's internal shadow roots for native form controls are therefore reported to the agent as author-written open shadow DOM.

Four call sites share the pattern:

browser_use/agent/prompts.py:197-206          the shadow(open)/shadow(closed) counts
browser_use/dom/serializer/serializer.py:1020 |SHADOW(...)| prefix
browser_use/dom/serializer/serializer.py:1105 |SHADOW(...)| prefix, second path
browser_use/dom/serializer/serializer.py:1146 "Open Shadow" / "Closed Shadow" text lines

Each is a variant of:

has_closed_shadow = ... shadow_root_type.lower() == 'closed'
shadow_prefix = '|SHADOW(closed)|' if has_closed_shadow else '|SHADOW(open)|'

Reproduction

No LLM or API key needed. Serve this over HTTP:

<select id="many"><option>one</option><option>two</option></select>
<button aria-label="Save">Icon</button>
<div role="button">Custom button</div>
<input value="prefilled">

Run python -m browser_use.dom.playground.extraction against it. From tmp/user_message.txt:

<page_stats>0 links, 4 interactive, 0 iframes, 2 shadow(open), 0 shadow(closed), 8 total elements</page_stats>
|SHADOW(open)|*[2]<select id=many ... />
|SHADOW(open)|[3]<input value=prefilled />

The page contains no author shadow DOM. In DevTools, document.querySelector('select').shadowRoot and document.querySelector('input').shadowRoot both return null.

The playground's own dump shows what CDP actually reported:

$ grep -o '"shadow_root_type": "[^"]*"' tmp/original_element_tree.json | sort | uniq -c
      8 "shadow_root_type": "user-agent"

Related

#4649 reported the volume of Open Shadow / Shadow End pairs in state output on sites like Reddit. Part of that volume is user-agent roots being labelled as open shadow DOM.

Which behaviour do you want?

  1. Report user-agent as its own category in the counts and markers
  2. Exclude user-agent shadow roots from the serialized tree — they're browser internals the agent can't act on and no page author wrote them
  3. Keep the counts but drop the |SHADOW(...)| marker and Open Shadow lines for them

I'd lean toward 2, but it changes what every agent sees on any page with a native form control, so I'd rather ask than guess. Happy to send a PR once you've picked.

Failing Python Code

# None - reproduced with `python -m browser_use.dom.playground.extraction`
# against the HTML above. No agent or LLM involved.

LLM Model

No response

Operating System & Browser Versions

No response

Full DEBUG Log Output

No error or traceback - the bug is incorrect output, not a failure.

$ grep -o '"shadow_root_type": "[^"]*"' tmp/original_element_tree.json | sort | uniq -c
      8 "shadow_root_type": "user-agent"

$ head -3 tmp/user_message.txt   # the serialized state the agent receives
<page_stats>0 links, 4 interactive, 0 iframes, 2 shadow(open), 0 shadow(closed), 8 total elements</page_stats>
|SHADOW(open)|*[2]<select id=many ... />
|SHADOW(open)|[3]<input value=prefilled />