fix(browser): stop false-flagging normal ChatGPT pages (incl. GPT-5.6 "Work" UI) as Cloudflare challenges#308
Conversation
…UI) as Cloudflare isCloudflareInterstitial returned true whenever the '/challenge-platform/' script was present, but Cloudflare bot-management injects that script on NORMAL ChatGPT pages too (including the new GPT-5.6 "Work" UI). So healthy pages were detected as challenges and the run was aborted with stage=cloudflare-challenge (which downstream tools then treated as a hard block / backed off on). Tighten detection to a real interstitial, in one injected predicate: - "Just a moment" / "Attention Required ... Cloudflare" title -> challenge (unchanged strong signal); - if the ChatGPT APP SHELL rendered (composer / conversation turns / profile / nav), it is never a challenge -> return false (this alone kills the false positive); - otherwise require a genuine marker: a Cloudflare challenge widget, verification copy, or the script on a SHORT interstitial-sized page (content-rich app pages never qualify). Extracted as buildCloudflareInterstitialExpression (exported for tests). Updated the ensureNotBlocked mocks (single boolean evaluate now) and added DOM-logic tests, including the regression: app shell + challenge script must NOT be flagged. Full browser suite green (646).
… window Review feedback on the fork run of this change: ensureNotBlocked executes right after navigation, which only waits for document.readyState, so a healthy SPA load can transiently look exactly like the weak-evidence case (bot-management script present, React app shell not yet rendered, short body) and still abort as cloudflare-challenge. Split the injected predicate into a verdict (strong / shell / weak). Strong evidence (challenge title, widget, verification copy) classifies immediately; the app shell clears immediately; weak script-only evidence must now persist through a 12s grace window in which the shell never appears. A real interstitial never grows an app shell, so the wait converts the race into a correct answer and only costs seconds on genuinely ambiguous pages. Regression tests: weak-then-shell resolves false, persistent weak resolves true, strong is immediate, no-evidence is immediate.
|
Pushed 10fc2de addressing review feedback from our fork run of this change: weak (script-only, shell-less, short-page) evidence must now persist through a 12s hydration grace window before classifying, closing the post-navigation race where a healthy SPA load transiently matches the weak case before React renders the app shell. Strong evidence (challenge title/widget/verification copy) still classifies immediately, and the rendered app shell still clears immediately. Full browser suite: 631 passing. |
|
Codex review: needs changes before merge. Reviewed July 10, 2026, 3:14 PM ET / 19:14 UTC. Summary Reproducibility: yes. Current-main source plus the submitted live CDP output establish the original script-only false positive, and the proposed verdict expression directly reproduces the new content-rich generic-body false positive without requiring a live account. Review metrics: 3 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge
Security Review findings
Review detailsBest possible solution: Preserve app-shell precedence and persistent weak script-only detection, but apply the short-page requirement to generic verification-copy evidence; add a content-rich shell-less false-positive fixture and rerun the browser smoke surface. Do we have a high-confidence way to reproduce the issue? Yes. Current-main source plus the submitted live CDP output establish the original script-only false positive, and the proposed verdict expression directly reproduces the new content-rich generic-body false positive without requiring a live account. Is this the best way to solve the issue? No as written. Positive evidence and app-shell precedence are the right direction, but generic body copy must inherit the short-page condition so the patch does not replace one false-positive trigger with another. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against f3fbc067c9d8. Label changesLabel changes:
Label justifications:
Evidence reviewedAcceptance criteria:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
|
Added the requested redacted real-behavior proof to the PR body: a live CDP capture of a healthy GPT-5.6 Work-UI page carrying the challenge script (the false-positive input, correctly cleared with zero latency), the overnight production ledger on the fork build (20 reviews, zero cloudflare-challenge aborts), and the before/after contrast with the 2026-07-09 incident. @clawsweeper re-review |
Problem
isCloudflareChallengetreated anyscript[src*="/challenge-platform/"]as proof of a Cloudflare interstitial. Cloudflare bot-management injects that script on perfectly normal pages, and ChatGPT's GPT-5.6 "Work" UI does so consistently: oracle then aborted healthy, signed-in sessions withstage=cloudflare-challengebefore ever submitting the prompt. Under an automation wrapper this is costly twice over: the run dies, and a wrapper that retries "into" the phantom challenge can trip real anti-bot defenses.Fix
Positive-evidence classification instead of script-presence inference:
#challenge-form/#cf-challenge-running/ turnstile iframe, or challenge body text) on a short, shell-less page.Validation
pnpm run typecheck,oxlint,oxfmt --check, full browser suite (627 passed) on this branch (cherry-picked clean onto currentmain, no dependency on fix(browser): finalize assistant turns on positive completion proof (stop preamble/mid-stream capture) #301).cloudflare-challengeaborts on GPT-5.6 Work-UI pages stopped entirely across dozens of long browser reviews (run ledger available on request), while genuine challenge detection still triggers on shell-less challenge pages (unit-tested for both directions).Independent of #301 (different file/mechanism); both apply cleanly together, which is how our fork runs them.
Real-behavior proof (redacted)
Healthy GPT-5.6 "Work" UI page, live CDP capture during an active production review (2026-07-10 14:5x EDT), read straight off the conversation tab:
{"generating":true,"testidStop":1,"oldSelectorMatches":1,"oldSelectorLabels":["Stop answering"],"newSelectorMatches":1,"newSelectorLabels":["Stop answering"],"pageWideStopLabels":["Stop answering"],"cf":{"hasAppShell":true,"hasChallengeScript":true,"bodyLen":59167,"titleIsChallenge":false},"visibleProgressBars":0}hasChallengeScript: trueon a page withhasAppShell: true, a 59 KB body, and a non-challenge title: this is exactly the false-positive input (the bot-management script on a healthy page). The verdict classifier clears it via the shell signal with zero added latency; the 12s grace never engages on this page.Production ledger (fork build with this change), overnight drain 2026-07-10 04:20-14:14 EDT: 20 long browser reviews, 18 clean and 2 unrelated capture failures, ZERO
cloudflare-challengeaborts. On 2026-07-09, before this fix, stock 0.15.2 aborted healthy Work-UI runs withCloudflare anti-bot page detectedon this same account (that incident motivated the patch).Genuine-challenge path: strong evidence (challenge title, widget, verification copy) classifies immediately, with latency unchanged from current main; that path is untouched by the grace window and pinned by the fixture tests. On the compounding concern: the grace runs at most once per
ensureNotBlockedcall (one navigation checkpoint), not per poll cycle, and only when the page has no app shell, no strong evidence, AND carries the script on a short body, a state that healthy pages exit as soon as hydration completes.