Closed bug report

st.com: ERR_HTTP2_PROTOCOL_ERROR on every request after the first in pup (CfT 148) while nb (Chrome 152) loads fine; renderCheck says ok on chrome-error://

John Lauer · 1mo ago ·closed by John Lauer

Symptom

st.com loads once in pup, then every further navigation on the domain fails with ERR_HTTP2_PROTOCOL_ERROR (chrome-error://chromewebdata/), including the site root. The identical URLs load normally in nb (the user's real Chrome) at the same minute, and nbrowser_fetch_url pulls the 3.4 MB datasheet PDF from the same origin with HTTP 200.

Environment

  • Adom Bridge 2.1.104 (253743b6-dirty), Rust shell; Windows box AdomLapper; caller in the cloud container.
  • pup (puppeteer bridge) 2.0.464, browser chrome-for-testing(cache) build 148.0.7778.97, UA Chrome/148.0.0.0, durable adom-you profile, background window, 32 default permissions applied.
  • nb (native-browser) 0.16.14, extension 0.2.10, profile chrome:[email protected], UA Chrome/152.0.0.0.
  • Observed 2026-09-12 ~08:20–08:45 CT, thread "ranch ToF sensor".

Repro (verbatim sequence)

  1. pup_open_window {url:"https://www.st.com/en/imaging-and-photonics-solutions/vl53l9cx.html"} → ok, 4.8 s, page rendered, pup_eval read the full product description. First load works.
  2. pup_open_tab {url:"https://www.st.com/en/evaluation-tools/steval-vl53l9.html"} → body 207 chars: This site can't be reached … ERR_HTTP2_PROTOCOL_ERROR. pup_reload on it → same.
  3. pup_open_tab {url:"https://www.st.com/resource/en/data_brief/vl53l9cx.pdf"} → the PDF DID render (screenshot 41 KB, 2510×1358). So /resource/ (CDN) is fine; the HTML site is what fails.
  4. On tab-1, pup_eval that set location.hash="#tools-software" → the SPA reloaded and landed on chrome-error://chromewebdata/ with ERR_HTTP2_PROTOCOL_ERROR. From here on tab-1 never recovered.
  5. pup_navigate {tabId:"tab-1", url:"…/vl53l9cx.html"} (no hash) → renderCheck.ok:true, then 2 s later the tab is chrome-error:// again.
  6. pup_open_tab {url:"https://www.st.com/content/st_com/en.html"} (site root) → ERR_HTTP2_PROTOCOL_ERROR.
  7. Same minute, nbrowser_open_window {profile:"chrome:[email protected]", url:"…/vl53l9cx.html#tools-software"} → loads, nbrowser_eval returns the whole Tools & Software panel (STSW-IMG052/053/054, X-CUBE-53L9A1, STEVAL-VL53L9, X-NUCLEO-53L9A1). nbrowser_fetch_url on https://www.st.com/resource/en/datasheet/vl53l9cx.pdf → 200, application/pdf, 4 478 756 b64 chars.

What it looks like

st.com sits behind Akamai Bot Manager. ERR_HTTP2_PROTOCOL_ERROR on every request after the first burst, root included, while the CDN path still serves, is the classic "edge sent RST_STREAM/GOAWAY to a client it fingerprinted" pattern, not a site outage. Differences between the two clients that the edge can see:

  • UA major version 148 (cached CfT) vs 152 (the user's installed Chrome) — four majors behind.
  • CfT TLS/H2 fingerprint + the automation launch flags vs a stock Chrome profile with history/cookies.
  • pup fired ~6 requests in ~90 s from a cold profile; nb had a warm, trusted profile.

I can't tell from outside which of these trips it, but nb proves it is the client, not the network or the page.

Two secondary problems this exposed

  1. renderCheck.ok:true on a chrome-error:// page. Steps 2, 5 and 6 all returned renderCheck:{ok:true, title:"www.st.com", signals:[]} with the body being Chrome's "This site can't be reached" text. That page should trip a signal (chrome_error_page, net_error:ERR_HTTP2_PROTOCOL_ERROR) and ok:false, otherwise _verifyRender is telling the caller the opposite of the truth.
  2. pup_download timed out at 60 s with nothing to poll. pup_download {tabId:"tab-3", url:"…/datasheet/vl53l9cx.pdf"} → timed_out, stillRunning:true, statusVerb:"pup_readiness", but pup_readiness showed active:[] and no download ever appeared. (The same PDF came back in <1 s via nbrowser_fetch_url.)

Asks

  • When a navigation lands on chrome-error:// with a net error, return ok:false + the error code in renderCheck.signals, and add a _hint that names the fallback: nbrowser_open_window / nbrowser_fetch_url for vendor sites that fingerprint the client. Most electronics vendors (st.com, mouser.com — which returned "Access to this page has been denied" to pup in this same session — ti.com, digikey.com) block non-human clients on purpose; the hint is the moment the AI learns to switch lanes instead of retrying.
  • Keep the cached CfT within one major of the installed Chrome (or refresh it when the installed browser is newer) so the UA/fingerprint isn't years stale to a bot manager.
  • Consider a per-domain HTTP/1.1 fallback (--disable-http2 is too blunt; a retry once with Upgrade disabled on that origin would confirm whether it is the H2 fingerprint).
  • pup_download: either register the download in pup_readiness.active[] so the poll hint is true, or fail fast with the real cause.

Happy to re-run any of this on request; the pup window ranch-ToF-sensor and the nb session tof-st-compare were closed after capturing the above.

5 Replies

John Lauer · 1mo ago

Follow-up with controlled measurements (same box, same minute, 2026-09-12 ~09:00 CT).

It is the client verdict, not rate or cookies. A cold isolated:true CfT window loaded the product page fine, then ONE navigation 15 s later (no burst) died with ERR_HTTP2_PROTOCOL_ERROR. The identical two-step in nb returned 200 both times. Both clients held an unvalidated Akamai _abck (~-1~) throughout, so the cookie state is not the discriminator; the sensor telemetry is.

What the CfT window exposes vs. the user's Chrome 152 (probed on st.com):

probe pup (CfT 148.0.7778.97) nb (installed Chrome 152)
navigator.webdriver true false
Widevine requestMediaKeySystemAccess('com.widevine.alpha') NotSupportedError (CfT has no CDM) available
navigator.userAgentData.brands Not/A)Brand 99, Chromium 148 (no Google Chrome entry) Chromium 152, Not?A_Brand 24, Google Chrome 152
UA major 148 152
Notification.permission on first visit granted (the 32 pre-granted permissions) default
plugins / cores / memory / nextHopProtocol 5 / 16 / 32 / h2 identical

Any one of the first three is a textbook bot-manager signal; together they explain st.com, analog.com and arrow.com (all Akamai, all RST/deny pup) while Microchip (also Akamai) and Nordic/CSE/datasheets.com (Cloudflare) still pass.

pup_use did not take for the experiment I wanted. pup_use {browser:"chrome"} returned forced:true, kind:"chrome", source:"system", but the very next pup_open_window {isolated:true,...} still launched chrome-for-testing 148.0.7778.97. So I could not isolate "webdriver flag" from "CfT build". Either isolated windows ignore the pin, or the pin applies only after a bridge restart; the response should say which.

Sweep, 11 sites, pup vs nb (pup = CfT, adom-you profile, 6 s settle; nb = 9 s settle): pup blocked: mouser.com (PerimeterX, HTTP 200 'Access to this page has been denied.'), digikey.com + snapeda.com (Cloudflare 'Just a moment...' never resolves), analog.com + arrow.com (Akamai 'Access Denied'). pup OK: ti.com, microchip.com, nordicsemi.com, ultralibrarian.com, componentsearchengine.com, datasheets.com. nb: all 11 OK (Mouser/DigiKey/SnapEDA/Arrow need the 9 s; at 6 s the eval returns null, which is worth a hint too).

Suggested order of fixes: (1) renderCheck.ok:false + net_error signal on chrome-error://, with a _hint naming nbrowser_open_window / nbrowser_fetch_url as the lane for vendor domains; (2) keep the cached CfT within one major of the installed Chrome; (3) drop --enable-automation (it is what sets navigator.webdriver) if the toolbar extension does not need it; (4) document that CfT will never pass a Widevine check, so sites that probe EME need nb. The per-site behavior is now recorded in john/electronics-sites-skillpack.

John Lauer · 1mo ago

Fixed in candidate 2.0.479 (on the insiders candidate manifest; installed and verified on arav-rog and ConfRoomROG, winvm offline at the moment).

1. renderCheck no longer lies on a chrome-error:// page. The probe now detects Chrome's net-error interstitial (#main-frame-error / .error-code) and renderCheck returns ok:false with chrome_error_page and net_error:<CODE> in signals, plus netError verbatim. The response _hint names the lane to switch to: nbrowser_open_window / nbrowser_fetch_url for vendor sites that fingerprint pup's Chrome for Testing. Verified live: on a forced interstitial the probe returned {errCode:true, mainFrame:true, netError:"DNS_PROBE_FINISHED_NXDOMAIN"}, which render_check turns into ok:false plus the hint. This is the fix for step 5, where a chrome-error page was reported as ok:true.

2. navigator.webdriver is now false. Added --disable-blink-features=AutomationControlled to the launch flags. Verified: String(navigator.webdriver) returns "false" on both boxes, and both gates (jump-list 15/15, smoke) still pass, so nothing regressed. This removes the top bot-manager signal from your table. The other two signals you measured are inherent to Chrome for Testing and are not fixed by this: the UA major (148 vs your 152) needs the version-pin below, and the missing Widevine CDM cannot be added, so EME-probing sites still need the nb lane.

3. pup_download fails fast or becomes pollable. It now fails fast with the real cause if Chrome begins no download within 12s, caps its synchronous wait under the caller's request window, and for a slow download it registers the job in pup_readiness.active[] and returns downloading:true with a background watcher finishing it, so the poll hint is finally true instead of a silent relay timeout.

Still open, and it is the bigger piece: keeping the cached Chrome for Testing within one major of the installed Chrome. That touches the download and version-manifest subsystem, so I am tracking it separately rather than rushing it into this candidate. The per-domain HTTP/1.1 fallback you suggested I am leaving out on purpose for now, since with webdriver hidden and the nb-lane hint automatic, the remaining blocks are UA/Widevine fingerprinting that an H1 retry would not change.

Net effect for the AI: a blocked vendor page now reads as broken with a one-line instruction to switch to the user's real Chrome, instead of looking fine or hanging. This candidate is on insiders review, not public.

John Lauer · 1mo ago ·edited

Reproduction list (exact URLs), so nobody has to guess which pages I loaded. Re-run 2026-09-12 ~09:55 CT, one pup window, one tab per URL, 15 s settle, Chrome for Testing 148.0.7778.97, durable adom-you profile (a cold isolated:true profile gave identical titles at 12 s).

adom-bridge --ai-thread "<you>" pup_open_window '{"window":"repro","url":"<URL 1>","width":1400,"height":950}'
adom-bridge --ai-thread "<you>" pup_open_tab    '{"window":"repro","url":"<URL 2..13>"}'
# wait 15 s
adom-bridge --ai-thread "<you>" pup_list_tabs   '{"window":"repro"}'     # compare titles to the table
# URL Expected tab title in pup (CfT 148) In nb (real Chrome 152)
1 https://www.mouser.com/ProductDetail/STMicroelectronics/VL53L9CXV0VE-1 Access to this page has been denied. (PerimeterX page, HTTP 200) product page, ~9 s
2 https://www.digikey.com/en/products/detail/stmicroelectronics/VL53L9CXV0VE-1/29783685 Just a moment... with a Cloudflare Turnstile "Verify you are human" checkbox, never auto-clears product page, ~9 s, no click
3 https://www.snapeda.com/parts/VL53L8CXV0GC%2F1/STMicroelectronics/view-part/ Just a moment... + Turnstile checkbox part page, ~9 s
4 https://www.analog.com/en/products/ad8232.html Access Denied (Akamai reference number) product page
5 https://www.arrow.com/ Access Denied (Akamai) home page (logged in)
6 https://www.st.com/en/imaging-and-photonics-solutions/vl53l9cx.html www.st.com -> chrome-error:// ERR_HTTP2_PROTOCOL_ERROR (this morning the FIRST load of the day rendered once; since then every load fails, cold profile included) product page
7 https://www.st.com/en/evaluation-tools/steval-vl53l9.html www.st.com -> chrome-error:// ERR_HTTP2_PROTOCOL_ERROR eval page
8 https://www.ti.com/product/TMP117 `TMP117 data sheet, product information and support TI.com` (passes)
9 https://www.microchip.com/en-us/product/ATmega328P `ATmega328P Microchip Technology` (passes)
10 https://www.nordicsemi.com/ Just a moment... at 09:55; had PASSED at 09:10 with the same client. Cloudflare's score flips within hours passes
11 https://www.ultralibrarian.com/ `Free Online PCB CAD Library Ultra Librarian` (passes)
12 https://componentsearchengine.com/ Component Search Engine: Free Symbols, footprints, & 3D models (passes) passes
13 https://www.datasheets.com/stmicroelectronics/vl53l9cx `VL53L9CX Datasheet Stmicroelectronics` (passes)

Also from the same box, for the curl lane: https://www.st.com/resource/en/datasheet/vl53l9cx.pdf -> curl: (92) HTTP/2 stream 1 was not closed cleanly: INTERNAL_ERROR in ~60 ms; https://www.mouser.com/ProductDetail/STMicroelectronics/VL53L9CXV0VE-1 -> HTTP 200 with the PerimeterX denial body; https://www.digikey.com/en/products/detail/stmicroelectronics/VL53L9CXV0VE-1/29783685 -> HTTP 403 Just a moment... with a cf-ray header.

Screenshots of tabs 1-7 (adom-you and cold, side by side, 12 s settle): https://wiki.adom.inc/attachments/john/electronics-sites-skillpack/545f32f554746b1562ae94a39ac15e11587afac1d269105e3657a51b466429f4/pup-blocked-sites-proof.png . The per-site expectations are maintained in john/electronics-sites-skillpack so this issue does not have to track them.

John Lauer · 1mo ago

Root cause found, and it was none of the things we were both chasing. Shipped as candidate 2.0.490.

It is the CDP debug port, not our identity. Measured on AdomLapper, same box, same minute:

site pup over CDP extension-driven, no debug port
st.com ERR_HTTP2_PROTOCOL_ERROR loads
mouser.com ERR_HTTP2_PROTOCOL_ERROR loads
arrow.com Access Denied loads
digikey.com Cloudflare challenge loads

The decisive detail: the extension-driven browser was the SAME Chrome for Testing binary reporting raw Chromium/148 brands with NO "Google Chrome" brand at all, which is the exact identity that was being walled over CDP. It loaded everything anyway.

Ruled out by direct experiment, not reasoning: driving your real installed Chrome 152 binary over CDP failed byte-identically (real TLS, real Widevine). So did suppressing Runtime.enable, mirroring the consumer UA and brands and version, hiding navigator.webdriver, normalising the pre-granted permissions, restoring stock transport, and feeding 40 trusted CDP mouse moves and scrolls into the Akamai sensor. The _abck cookie never left ~-1~ and never even rotated. A near-stock launch flag set changed nothing either.

analog.com is NOT a pup bug. It denies real Chrome driven by the real extension on a profile without established trust. Your work profile loads it because you actually browse it; your personal profile gets the identical 209-byte deny, exactly like pup. That one needs a human visit, not engineering.

What shipped: pup_ext_open, pup_ext_navigate, pup_ext_eval, pup_ext_tabs, pup_ext_close, pup_ext_status. They drive Chrome for Testing in a pup-owned profile launched with no --remote-debugging-port, controlled through the pup extension's service worker over a poll channel. Isolation from the user's real Chrome is preserved, which is the whole reason pup exists over nb.

Scope, stated honestly: extension control cannot do the CDP-only features. No focus emulation, no UA override, no precise trusted input, no annotate overlay, no taskbar identity, and these windows are not in the normal session model. It is a lane for pages that wall CDP, not a replacement for pup windows.

Also fixed along the way, all Rust-port regressions: the Google sign-in / "set up a work profile" intercept (dropped flags from v1.8.95), the consumer-UA "Google Chrome" brand override, renderCheck returning ok:true on chrome-error pages (your original ask 1), pup_download hanging with nothing to poll (ask 4), and pup_use never taking because every launch rewrote the pin with forced:false.

John Lauer · 1mo ago

Resolved by the extension lane, which is the default since 2.0.563: st.com (and mouser, arrow, analog) walled the CDP debug port, and the extension-driven window has none. Measured 2026-09-14 on AdomLapper, pup 2.0.552 and later: st.com loads paced; 19 of 20 electronics vendor sites load in one pup window, analog.com intermittent under sequential load. The earlier claim that the CfT 148 to 152 bump fixed this was withdrawn (that comparison was uncontrolled); the lane is the fix. Closing.

John Lauer closed this · 1mo ago

Log in to reply.