Skip to content

Comments

[fix]: ctx addInitScript on popup pages#1642

Merged
seanmcguire12 merged 3 commits intomainfrom
seanmcguire/stg-1259-fix-contextaddinitscript
Feb 2, 2026
Merged

[fix]: ctx addInitScript on popup pages#1642
seanmcguire12 merged 3 commits intomainfrom
seanmcguire/stg-1259-fix-contextaddinitscript

Conversation

@seanmcguire12
Copy link
Member

@seanmcguire12 seanmcguire12 commented Jan 30, 2026

why

  • fixes context.addInitScript is not applied on new pages #1628
  • scripts injected viacontext.addInitScript() were not reliably applied to pages that were created via a popup (eg, clicking a link that opens a new page), or, when a new page was created via context.newPage(url).
  • when a target was created directly at a URL, navigation often started before attaching to the target & installing the init script. this means that the script would either run too late or not run at all.

what changed

  • enabled waitForDebuggerOnStart in Target.setAutoAttach in cdp.ts, so that new targets pause until we explicitly resume them
  • in understudy/context.ts:
    • onAttachedToTarget() now installs context init scripts on the session before resuming, then resumes with Runtime.runIfWaitingForDebugger. this guarantees scripts are registered before the first real document executes.
    • worker targets are resumed immediately. we still are not attaching to them, but they need to be resumed since we are now telling everything to wait for the debugger.
    • added seedOnly to applyInitScriptsToPage() so the page object records scripts without re‑installing them
    • newPage() first creates targets at about:blank and then navigates after attach (fire‑and‑forget). this avoids the race where the first document loads before init scripts are installed
  • inunderstudy/page.ts, i added seedInitScript() so the page can track scripts that were already installed on the session

test plan

  • added additional tests to tests/context-addInitScript.spec.ts which explicitly covers context.newPage(url) and popup pages to ensure init scripts run on the initial document

Summary by cubic

Fixes a race where context.addInitScript() didn’t run on the first document for new pages opened via popups or context.newPage(url). New targets now pause until init scripts are registered, ensuring scripts run reliably.

  • Bug Fixes
    • Paused new targets with waitForDebuggerOnStart; resume after script install.
    • Installed init scripts on the CDP session before first document executes.
    • Resumed worker targets immediately so they don’t stay paused.
    • Created newPage() at about:blank, attach, then navigate (fire-and-forget).
    • Seeded scripts/URL on Page to avoid double installs and timing issues.
    • Added tests for newPage(url) and popup pages to verify scripts run on initial load.

Written for commit 21193c2. Summary will update on new commits. Review in cubic

@changeset-bot
Copy link

changeset-bot bot commented Jan 30, 2026

🦋 Changeset detected

Latest commit: 21193c2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@browserbasehq/stagehand Patch
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 30, 2026

Greptile Overview

Greptile Summary

This PR fixes a race condition where context.addInitScript() didn't reliably run on the initial document when pages were created via popups or context.newPage(url). The fix pauses new targets using waitForDebuggerOnStart, installs init scripts at the session level before resuming, and ensures newPage(url) creates targets at about:blank before navigating.

Key changes:

  • Enabled waitForDebuggerOnStart in CDP auto-attach to pause all new targets until init scripts are installed
  • Installed context-level init scripts on the CDP session before resuming targets, ensuring scripts are registered before the first document executes
  • Modified newPage(url) to create targets at about:blank, wait for attachment, then navigate fire-and-forget to avoid race conditions
  • Added seedOnly option to prevent double-installing init scripts that add DOMContentLoaded handlers
  • Worker targets are now explicitly resumed to prevent deadlocks (workers are ignored by Stagehand but must be resumed)
  • Comprehensive tests verify init scripts run on initial documents for both newPage(url) and popup scenarios

Confidence Score: 4/5

  • This PR is safe to merge with careful monitoring of the new timing behavior
  • The fix addresses a critical race condition with a well-thought-out solution. The use of waitForDebuggerOnStart and session-level script installation before resuming is architecturally sound. Comprehensive tests cover the key scenarios. Minor confidence reduction due to the complexity of the timing changes and the potential for edge cases in the wild (worker handling, OOPIF scenarios, error paths during script installation).
  • No files require special attention

Important Files Changed

Filename Overview
packages/core/lib/v3/understudy/cdp.ts Enabled waitForDebuggerOnStart and removed worker filter to pause all new targets until explicitly resumed
packages/core/lib/v3/understudy/context.ts Major refactor: added installInitScriptsOnSession(), modified onAttachedToTarget() with resume logic, and changed newPage() to create targets at about:blank then navigate

Sequence Diagram

sequenceDiagram
    participant Client
    participant Context
    participant CDP
    participant Session
    participant Browser
    
    Note over Client,Browser: newPage(url) Flow
    Client->>Context: newPage(targetUrl)
    Context->>CDP: Target.createTarget({url: "about:blank"})
    CDP->>Browser: Create target at about:blank
    Browser-->>CDP: targetId
    CDP-->>Context: {targetId}
    Context->>Context: Store targetId with "about:blank"
    
    Note over Browser,Session: Auto-attach with waitForDebuggerOnStart
    Browser->>CDP: Target.attachedToTarget (paused)
    CDP->>Context: onAttachedToTarget(info, sessionId)
    
    alt Worker Target
        Context->>Session: Runtime.runIfWaitingForDebugger
        Session->>Browser: Resume worker
    else Page Target
        Context->>Context: executionContexts.attachSession()
        Context->>Session: installInitScriptsOnSession()
        Session->>Browser: Page.enable
        Session->>Browser: Page.addScriptToEvaluateOnNewDocument(script1)
        Session->>Browser: Page.addScriptToEvaluateOnNewDocument(script2)
        Context->>Session: Runtime.runIfWaitingForDebugger
        Session->>Browser: Resume target
        Browser-->>Context: Scripts settled
        
        Context->>Context: Page.create()
        Context->>Context: applyInitScriptsToPage({seedOnly: true})
        Note over Context: Seed only to avoid double-install
        Context->>Context: Register page in pagesByTarget
        Context-->>Client: page object returned
        
        Client->>Context: (newPage continues)
        Context->>Context: page.seedCurrentUrl(targetUrl)
        Context->>Session: Page.navigate({url: targetUrl})
        Note over Session,Browser: Fire-and-forget navigation
        Session->>Browser: Navigate to actual URL
        Browser->>Browser: Init scripts run on first document
    end
    
    Note over Client,Browser: Popup Flow
    Client->>Browser: Click link with target="_blank"
    Browser->>CDP: Target.attachedToTarget (paused, with openerId)
    CDP->>Context: onAttachedToTarget(info, sessionId)
    Context->>Session: installInitScriptsOnSession()
    Session->>Browser: Page.enable
    Session->>Browser: Page.addScriptToEvaluateOnNewDocument(scripts)
    Context->>Session: Runtime.runIfWaitingForDebugger
    Session->>Browser: Resume popup target
    Browser->>Browser: Init scripts run on popup document
    Context->>Context: Page.create() and register
    Context->>Context: applyInitScriptsToPage({seedOnly: true})
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

"@browserbasehq/stagehand": patch
---

fix issue where scripts added via context.addInitScripts() were not being injected into new pages that were opened via popups (eg, clicking a link that opens a new page) and/or calling context.newPage(url)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: addInitScripts() should be addInitScript() (no 's' at the end)

Prompt To Fix With AI
This is a comment left during a code review.
Path: .changeset/small-jobs-drum.md
Line: 5:5

Comment:
typo: `addInitScripts()` should be `addInitScript()` (no 's' at the end)

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files

Confidence score: 2/5

  • High risk due to packages/core/lib/v3/understudy/context.ts not awaiting CDP send() calls, so scripts may not be installed before the target resumes, leading to inconsistent behavior.
  • The issue is concrete and user-facing, which lowers merge confidence despite the change being limited in scope.
  • Pay close attention to packages/core/lib/v3/understudy/context.ts - CDP send() calls are fire-and-forget, so setup may race with target resume.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/core/lib/v3/understudy/context.ts">

<violation number="1" location="packages/core/lib/v3/understudy/context.ts:66">
P1: CDP `send()` calls are not awaited - scripts may not be installed before target resumes.

The method is `async` but none of the `send()` calls use `await`. The `.catch(() => {})` only handles errors, it doesn't wait for the promises. This defeats the PR's goal of guaranteeing scripts are registered before the first document executes.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant C as Client
    participant VC as V3Context
    participant CDP as CdpConnection (Browser)
    participant S as Target Session (CDP)
    participant P as Page Object

    Note over VC,CDP: Initialization
    VC->>>CDP: CHANGED: Target.setAutoAttach(waitForDebuggerOnStart: true)

    rect rgb(23, 37, 84)
    Note over C,S: NEW: Page Creation Flow (e.g. newPage(url) or Popup)
    C->>>VC: newPage(targetUrl)
    VC->>>CDP: Target.createTarget(url: "about:blank")
    CDP-->>VC: targetId
    
    CDP->>VC: onAttachedToTarget(info, sessionId)
    Note right of VC: Target is PAUSED by debugger
    
    VC->>>VC: NEW: installInitScriptsOnSession(session)
    VC->>>S: Page.addScriptToEvaluateOnNewDocument(sources)
    
    VC->>>S: NEW: Runtime.runIfWaitingForDebugger
    Note right of S: Target resumes execution
    
    VC->>>P: NEW: Page.create() & seedInitScript()
    VC->>>P: seedCurrentUrl(targetUrl)
    
    opt targetUrl != "about:blank"
        VC->>>S: CHANGED: Page.navigate(targetUrl)
    end
    
    VC-->>C: return Page
    end

    rect rgb(69, 26, 3)
    Note over VC,S: Worker Handling
    CDP->>VC: onAttachedToTarget(workerInfo)
    VC->>>S: NEW: Runtime.runIfWaitingForDebugger
    Note right of S: Resume workers immediately (not tracked)
    end

    Note over VC,P: Dynamic Script Addition
    C->>>VC: addInitScript(script)
    VC->>>P: registerInitScript(source)
    P->>>S: Page.addScriptToEvaluateOnNewDocument(source)
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@seanmcguire12
Copy link
Member Author

@cubic-dev-ai

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Jan 30, 2026

@cubic-dev-ai

@seanmcguire12 I have started the AI code review. It will take a few minutes to complete.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files

Confidence score: 3/5

  • Potential user-impacting bug: scriptsInstalled in packages/core/lib/v3/understudy/context.ts doesn’t reflect async failures from Promise.allSettled, so session-level install failures may prevent page fallback script installation.
  • Score lowered due to a concrete, medium-high severity logic gap around failure handling that could affect runtime behavior.
  • Pay close attention to packages/core/lib/v3/understudy/context.ts - ensure settled results are checked so failures trigger fallback installation.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/core/lib/v3/understudy/context.ts">

<violation number="1" location="packages/core/lib/v3/understudy/context.ts:464">
P1: The `scriptsInstalled` flag never reflects async failures from `Promise.allSettled`. If session-level script installation fails, scripts won't be installed via the page fallback. Check the settled results to update the flag.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client
    participant Ctx as V3Context
    participant CDP as Browser (CDP)
    participant Page as Page Object

    Note over Ctx,CDP: CHANGED: Target.setAutoAttach<br/>(waitForDebuggerOnStart: true)

    Client->>Ctx: newPage("https://site.com")
    
    Note over Ctx,CDP: Phase 1: Create Paused Target
    Ctx->>CDP: CHANGED: Target.createTarget(url="about:blank")
    Note right of CDP: Target created PAUSED
    CDP-->>Ctx: targetId

    par Event Processing
        CDP->>Ctx: event: attachedToTarget
        
        alt Target is Worker
            Ctx->>CDP: Runtime.runIfWaitingForDebugger
        else Target is Page
            Note over Ctx,CDP: Phase 2: Secure Init Injection
            Ctx->>CDP: NEW: Page.enable
            Ctx->>CDP: NEW: Page.addScriptToEvaluateOnNewDocument
            Ctx->>CDP: NEW: Runtime.runIfWaitingForDebugger
            Note right of CDP: Resumes (at about:blank)
            
            Ctx->>Page: create(session)
            Ctx->>Page: NEW: seedInitScript(source)
            Note right of Page: Record script (skip re-install)
            Ctx->>Ctx: Register Page instance
        end

    and Waiting & Navigation
        loop Wait for Page instance
            Ctx->>Ctx: Check pagesByTarget.get(targetId)
        end
        
        Note over Ctx,CDP: Phase 3: Navigate to Real URL
        Ctx->>Page: seedCurrentUrl("https://site.com")
        Ctx->>CDP: NEW: Page.navigate("https://site.com")
    end
    
    Ctx-->>Client: return Page
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@seanmcguire12
Copy link
Member Author

@cubic-dev-ai

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Jan 30, 2026

@cubic-dev-ai

@seanmcguire12 I have started the AI code review. It will take a few minutes to complete.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant C as V3Context
    participant CONN as CdpConnection
    participant B as Browser (CDP)
    participant S as Target Session
    participant P as Page Object

    Note over C,B: NEW: page/popup creation flow with guaranteed script injection

    C->>CONN: newPage(url)
    CONN->>B: Target.createTarget(url: "about:blank")
    Note right of B: NEW: waitForDebuggerOnStart pauses execution
    B-->>CONN: targetId

    B->>CONN: Target.attachedToTarget (Event)
    CONN->>C: onAttachedToTarget(info, sessionId)

    rect rgb(23, 37, 84)
        Note over C,S: NEW: Pre-execution script injection
        C->>S: Page.enable
        loop for each initScript
            C->>S: Page.addScriptToEvaluateOnNewDocument(source)
        end
        C->>S: CHANGED: Runtime.runIfWaitingForDebugger
    end

    alt is worker target
        C->>S: CHANGED: Resume immediately (not tracked)
    else is top-level page
        C->>P: Page.create()
        C->>P: CHANGED: seedInitScript() (track scripts without re-injecting)
        
        opt url is not about:blank
            P->>P: seedCurrentUrl(targetUrl)
            P->>S: CHANGED: Page.navigate(targetUrl)
        end
    end

    C-->>C: resolve newPage()
    
    Note over S,B: Browser executes init scripts before actual document loads
Loading

@seanmcguire12
Copy link
Member Author

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

{ type: "shared_worker", exclude: true },
{ type: "service_worker", exclude: true },
],
waitForDebuggerOnStart: true,
Copy link
Member

@pirate pirate Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might have a stealth/reliability impact, gaps in runtime performance metrics as a result of debuggerOnStart are one of the signals bot-blockers use iirc. Also there are weird situations with using waitForDebuggerOnStart on service workers.

might be worth only setting this if there are initScripts needed

Further reading on issues with waitForDebuggerOnStart:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

context.addInitScript is not applied on new pages

2 participants