Skip to content

[fix]: ctx.addInitScript() iframe issues#1664

Merged
seanmcguire12 merged 13 commits intomainfrom
fix-ctx-add-init-script-iframes
Feb 6, 2026
Merged

[fix]: ctx.addInitScript() iframe issues#1664
seanmcguire12 merged 13 commits intomainfrom
fix-ctx-add-init-script-iframes

Conversation

@seanmcguire12
Copy link
Member

@seanmcguire12 seanmcguire12 commented Feb 5, 2026

why

  • scripts added viacontext.addInitScript() were not being applied in the following scenarios:
    • for pages with OOPIFs, the script was not being applied to the iframe
    • for pages with SPIFs, the script was not being applied to the iframe if the page was opened via a popup

what changed

  • added runImmediately: true to Page.addScriptToEvaluateOnNewDocument calls to so that scripts execute before any other code runs
  • refactored target attachment to propagate Target.setAutoAttach with waitForDebuggerOnStart: true to nested sessions, ensuring OOPIFs are paused before they can execute
  • moved target resume (Runtime.runIfWaitingForDebugger) to happen after init scripts are registered to prevent init scripts being missed
  • added per-session tracking of Target.attachedToTarget listeners to properly handle nested iframe attachment chains
  • removed the explicit Target.targetCreated fallback attach logic, relying instead on the properly configured auto-attach cascade
  • removed runImmediately: true from page.installInitScriptOnSession to avoid double-execution when addInitScript is called after pages are live
  • pre-register the piercer script in the install promises before target resume, and short-circuit ensurePiercer() on success. post-resume sequential CDP round-trips were delaying Page.create/installFrameEventBridges enough to miss SPIF attachment events
  • clean up _targetSessionListeners, _sessionInit, and _piercerInstalled sets in onDetachedFromTarget to prevent accumulation
  • also hardened the tests in iframe-ctx-addInitScript.spec.ts so that (1) they are accurately comparing frames, and (2) they are not using hardcoded timeouts

test plan


Summary by cubic

Fixes context.addInitScript so init scripts run in OOPIFs and in popup pages with SPIF iframes. Scripts now execute before any page code and reliably propagate across nested targets.

  • Bug Fixes
    • Run init scripts immediately via Page.addScriptToEvaluateOnNewDocument.
    • Cascade Target.setAutoAttach with waitForDebuggerOnStart to nested sessions to pause OOPIFs.
    • Resume targets after registering init scripts to prevent misses.
    • Track per-session Target event listeners for nested iframe chains.
    • Remove Target.targetCreated fallback; rely on auto-attach cascade.
    • Register the piercer as an init script (with immediate eval fallback) so it runs before page scripts across frames.
    • Unskip iframe tests for context.addInitScript.

Written for commit 40d65b9. Summary will update on new commits. Review in cubic

@changeset-bot
Copy link

changeset-bot bot commented Feb 5, 2026

🦋 Changeset detected

Latest commit: 40d65b9

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

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 6 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant B as Browser / CDP
    participant C as V3Context
    participant S as CDPSession (Page/OOPIF)
    participant P as Piercer / ExecutionContext

    Note over B,S: Target Attachment & Init Script Flow

    B-->>C: Target.attachedToTarget (Target Paused)
    C->>C: NEW: installTargetSessionListeners()
    
    rect rgb(23, 37, 84)
    Note right of C: Session Initialization (onAttachedToTarget)
    C->>S: Page.enable()
    C->>S: Runtime.enable()
    
    C->>S: NEW: Target.setAutoAttach(waitForDebuggerOnStart: true, flatten: true)
    Note right of S: Ensures nested OOPIFs also pause on start
    
    opt Has Init Scripts
        C->>S: CHANGED: Page.addScriptToEvaluateOnNewDocument(source, runImmediately: true)
    end
    
    C->>S: NEW: Runtime.runIfWaitingForDebugger()
    Note right of S: Target resumes ONLY after scripts are registered
    end

    Note over C,P: Piercer Installation Flow

    C->>S: NEW: Page.getFrameTree()
    S-->>C: frameTree (All nested frames)
    
    loop Each Frame in Tree
        C->>P: NEW: waitForMainWorld(frameId)
        P-->>C: executionContextId
        C->>S: Runtime.evaluate(v3ScriptContent, contextId)
        C->>S: Runtime.evaluate(reRenderScriptContent, contextId)
    end

    B-->>C: NEW: Page.frameNavigated (Event)
    C->>P: Re-install piercer in new main world context

    alt Unhappy Path: Session Closed
        S-->>C: Error: Session with given id not found
        C->>C: Cleanup session listeners & tracking
    end
Loading

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 5, 2026

Greptile Overview

Greptile Summary

This PR changes V3Context target attachment/init-script ordering so context.addInitScript() reliably applies to OOPIFs and to popup pages with same-process iframes. It cascades Target.setAutoAttach with waitForDebuggerOnStart, registers init scripts via Page.addScriptToEvaluateOnNewDocument({ runImmediately: true }) before resuming targets, and pre-registers the piercer script to reduce post-resume CDP round-trips that could miss early iframe events. Tests for iframe behavior are unskipped and updated to poll for the child frame instead of sleeping.

Key issues to address before merge are around lifecycle cleanup and piercer installation tracking: the new per-session Target.* listeners are added but not removed on detach, and the _piercerInstalled keying now uses session.id ?? "" which can incorrectly short-circuit piercer installation for root sessions.

Confidence Score: 3/5

  • This PR is close to mergeable but needs fixes for listener lifecycle cleanup and piercer installation bookkeeping.
  • Core change is scoped and test-updated, but there are concrete correctness/maintenance issues: session-level Target listeners are not removed on detach (can cause duplicate event handling/leaks), and _piercerInstalled keying with an empty-string fallback can incorrectly short-circuit installation for root sessions.
  • packages/core/lib/v3/understudy/context.ts

Important Files Changed

Filename Overview
.changeset/curvy-suns-invite.md Adds a patch changeset entry describing the addInitScript iframe fix.
packages/core/lib/v3/tests/iframe-ctx-addInitScript.spec.ts Unskips iframe addInitScript tests and replaces fixed sleeps with polling for a loaded child frame.
packages/core/lib/v3/understudy/context.ts Refactors target auto-attach/init-script ordering for OOPIF/SPIF; introduces session-level Target listeners but does not remove them on detach, and changes piercer-installed keying in a way that can incorrectly short-circuit for root sessions.

Sequence Diagram

sequenceDiagram
  autonumber
  participant Chrome as Chrome/CDP
  participant Conn as CdpConnection (root)
  participant Ctx as V3Context
  participant Sess as Target Session (page/iframe)
  participant Page as Page (owner)

  Chrome->>Conn: Target.attachedToTarget(targetInfo, sessionId)
  Conn->>Ctx: onAttachedToTarget(targetInfo, sessionId)
  Ctx->>Conn: getSession(sessionId)
  Conn-->>Ctx: Sess

  Ctx->>Sess: Target.setAutoAttach(autoAttach=true, waitForDebuggerOnStart=true, flatten=true)
  Ctx->>Sess: Page.enable
  Ctx->>Sess: Runtime.enable
  loop for each ctx initScript
    Ctx->>Sess: Page.addScriptToEvaluateOnNewDocument(source, runImmediately=true)
  end
  Ctx->>Sess: Page.addScriptToEvaluateOnNewDocument(v3ScriptContent, runImmediately=true)

  Ctx->>Sess: Runtime.runIfWaitingForDebugger (resume)

  alt top-level page target
    Ctx->>Page: Page.create(conn, session, targetId)
    Ctx->>Ctx: installFrameEventBridges(sessionId, Page)
    Ctx->>Page: applyInitScriptsToPage(seedOnly=scriptsInstalled)
  else OOPIF/iframe target
    Ctx->>Sess: Page.getFrameTree
    Sess-->>Ctx: childMainFrameId
    Ctx->>Page: adoptOopifSession(session, childMainFrameId) (if owner found)
    Ctx->>Ctx: installFrameEventBridges(sessionId, owner)
  end

  Chrome-->>Sess: Target.detachedFromTarget(sessionId, targetId)
  Sess->>Ctx: onDetachedFromTarget(sessionId, targetId)
  Ctx->>Page: detachOopifSession(sessionId)
  Ctx->>Ctx: cleanup maps / sets
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, 2 comments

Edit Code Review Agent Settings | Greptile

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 4 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant Ctx as V3Context
    participant Conn as CdpConnection
    participant RootSess as CDPSession (Page)
    participant SubSess as CDPSession (OOPIF/Frame)
    participant Browser as Browser Target

    Note over Ctx,Browser: Target Initialization & Auto-Attach Cascade

    Conn->>Ctx: Target.targetCreated (Page)
    Ctx->>RootSess: Initialize Session

    rect rgb(23, 37, 84)
    Note right of Ctx: NEW: Session Bootstrap (Ordered for Timing)
    Ctx->>RootSess: Page.enable / Runtime.enable
    Ctx->>RootSess: CHANGED: Target.setAutoAttach (waitForDebuggerOnStart: true)
    
    loop For each Init Script
        Ctx->>RootSess: NEW: Page.addScriptToEvaluateOnNewDocument (runImmediately: true)
    end
    
    Ctx->>RootSess: NEW: Register Piercer via addScriptToEvaluateOnNewDocument
    Ctx->>RootSess: CHANGED: Runtime.runIfWaitingForDebugger (Resume)
    end

    Browser-->>RootSess: Target.attachedToTarget (Nested OOPIF)
    
    rect rgb(23, 37, 84)
    Note over Ctx,SubSess: NEW: Hierarchical Attachment Propagation
    RootSess->>Ctx: NEW: Target.attachedToTarget Event
    Ctx->>SubSess: Initialize Session (Recursive)
    Ctx->>SubSess: Page.enable / Runtime.enable
    Ctx->>SubSess: NEW: Target.setAutoAttach (waitForDebuggerOnStart: true)
    
    Note over SubSess,Browser: Frame is paused by DevTools
    
    Ctx->>SubSess: Page.addScriptToEvaluateOnNewDocument (Init Scripts)
    Ctx->>SubSess: Page.addScriptToEvaluateOnNewDocument (Piercer Script)
    
    Ctx->>SubSess: CHANGED: Runtime.runIfWaitingForDebugger (Resume Subframe)
    end

    Note over Ctx,SubSess: Cleanup Flow
    SubSess->>Ctx: Target.detachedFromTarget
    Ctx->>Ctx: Remove Session Listeners & Cleanup Sets
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.

4 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@browserbase browserbase deleted a comment from greptile-apps bot Feb 5, 2026
@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.

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 5, 2026

Additional Comments (1)

packages/core/lib/v3/understudy/context.ts
_targetSessionListeners never cleaned up on session detach. In long-lived contexts with many session attach/detach cycles, this could accumulate stale session IDs.

  private onDetachedFromTarget(
    sessionId: SessionId,
    targetId: string | null,
  ): void {
    const owner = this.sessionOwnerPage.get(sessionId);
    if (owner) {
      owner.detachOopifSession(sessionId);
      this.sessionOwnerPage.delete(sessionId);
    }

    if (targetId && this.pagesByTarget.has(targetId)) {
      this.cleanupByTarget(targetId);
    }

    for (const [fid, sid] of Array.from(
      this.pendingOopifByMainFrame.entries(),
    )) {
      if (sid === sessionId) this.pendingOopifByMainFrame.delete(fid);
    }

    this._targetSessionListeners.delete(sessionId);
  }
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/lib/v3/understudy/context.ts
Line: 617:636

Comment:
`_targetSessionListeners` never cleaned up on session detach. In long-lived contexts with many session attach/detach cycles, this could accumulate stale session IDs.

```suggestion
  private onDetachedFromTarget(
    sessionId: SessionId,
    targetId: string | null,
  ): void {
    const owner = this.sessionOwnerPage.get(sessionId);
    if (owner) {
      owner.detachOopifSession(sessionId);
      this.sessionOwnerPage.delete(sessionId);
    }

    if (targetId && this.pagesByTarget.has(targetId)) {
      this.cleanupByTarget(targetId);
    }

    for (const [fid, sid] of Array.from(
      this.pendingOopifByMainFrame.entries(),
    )) {
      if (sid === sessionId) this.pendingOopifByMainFrame.delete(fid);
    }

    this._targetSessionListeners.delete(sessionId);
  }
```

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

@browserbase browserbase deleted a comment from cubic-dev-ai bot Feb 6, 2026
@browserbase browserbase deleted a comment from cubic-dev-ai bot Feb 6, 2026
@browserbase browserbase deleted a comment from greptile-apps bot Feb 6, 2026
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, 3 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 6, 2026

Additional Comments (2)

packages/core/lib/v3/understudy/context.ts
Double resume call

resume() is added to installPromises (line ~504) and called again unconditionally in the finally block (line ~607). Since resume() is guarded by a local resumed flag, this won’t deadlock, but it also means the finally resume can run before the init-script Promise.allSettled(installPromises) completes if an exception is thrown after the try (e.g. ensurePiercer() early-returns), defeating the stated goal of “resume after init scripts are registered”. Consider making finally resume conditional on resumed state and ensuring the only resume happens after init-script registration finishes, even on early returns.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/lib/v3/understudy/context.ts
Line: 458:463

Comment:
**Double resume call**

`resume()` is added to `installPromises` (line ~504) *and* called again unconditionally in the `finally` block (line ~607). Since `resume()` is guarded by a local `resumed` flag, this won’t deadlock, but it also means the `finally` resume can run before the init-script `Promise.allSettled(installPromises)` completes if an exception is thrown after the `try` (e.g. `ensurePiercer()` early-returns), defeating the stated goal of “resume after init scripts are registered”. Consider making `finally` resume conditional on `resumed` state *and* ensuring the only resume happens after init-script registration finishes, even on early returns.

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

packages/core/lib/v3/tests/iframe-ctx-addInitScript.spec.ts
Flaky sleep-based wait

These tests use a fixed setTimeout(1000) to “wait for iframe to load”. With the suite now unskipped, this will be flaky across CI / Browserbase. Prefer a deterministic wait (e.g. wait until page.frames().length > 1, wait for a known selector in the iframe, or poll the computed background color in the child frame until it matches).

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/lib/v3/tests/iframe-ctx-addInitScript.spec.ts
Line: 38:40

Comment:
**Flaky sleep-based wait**

These tests use a fixed `setTimeout(1000)` to “wait for iframe to load”. With the suite now unskipped, this will be flaky across CI / Browserbase. Prefer a deterministic wait (e.g. wait until `page.frames().length > 1`, wait for a known selector in the iframe, or poll the computed background color in the child frame until it matches).

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 3 files

Confidence score: 4/5

  • This PR is likely safe to merge; the only noted issue is low-to-moderate severity and localized in session cleanup logic.
  • In packages/core/lib/v3/understudy/context.ts, _piercerInstalled is deleted using raw sessionId while insertion uses sessionKey(session), which could leave stale entries if session.id is null or differs, leading to inconsistent cleanup.
  • Pay close attention to packages/core/lib/v3/understudy/context.ts - ensure consistent keying for _piercerInstalled insert/delete.
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:639">
P2: Inconsistent key used when deleting from `_piercerInstalled`. The set is populated using `sessionKey(session)` which returns `session.id ?? "root"`, but deletion uses raw `sessionId`. Use consistent keying to ensure proper cleanup: `this._piercerInstalled.delete(this.sessionKey({ id: sessionId } as CDPSessionLike));` or use raw `sessionId` consistently in both add and delete operations.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as User / Test Code
    participant Ctx as V3Context
    participant Conn as CdpConnection
    participant Session as CDPSession (Page/OOPIF)
    participant Browser as Browser Target

    Note over Client,Browser: Context & Script Setup
    Client->>Ctx: addInitScript(source)
    Ctx->>Ctx: Store source in initScripts[]

    Note over Ctx,Browser: Target Discovery & Initialization
    Conn->>Ctx: Target.attachedToTarget (TargetInfo, SessionId)
    Ctx->>Ctx: NEW: installTargetSessionListeners()
    Ctx->>Session: Page.enable()
    Ctx->>Session: Runtime.enable()

    Note over Ctx,Session: Configuration (Before Code Execution)
    Ctx->>Session: NEW: Target.setAutoAttach(waitForDebuggerOnStart: true)
    Note right of Session: Ensures sub-frames (OOPIFs) pause before execution

    loop For each script in initScripts
        Ctx->>Session: NEW: Page.addScriptToEvaluateOnNewDocument(runImmediately: true)
    end

    Ctx->>Session: NEW: Register Shadow-DOM Piercer (Init Script)
    Note right of Session: runImmediately: true ensures shadow-hooks lead page scripts

    Note over Ctx,Browser: Execution Resume
    Ctx->>Session: CHANGED: Runtime.runIfWaitingForDebugger()
    Session-->>Browser: Target starts executing scripts

    Note over Session,Browser: Nested Iframe / OOPIF Flow
    Browser->>Session: Target.attachedToTarget (Nested Iframe)
    Session->>Ctx: NEW: Propagate Event (Session-tracked listener)
    Ctx->>Ctx: Recursive Attachment Flow (Paused by auto-attach)
    Ctx->>Ctx: Repeat Session Initialization for Sub-target

    Note over Ctx,Browser: Cleanup
    Browser-->>Session: Target.targetDestroyed
    Session-->>Ctx: Event
    Ctx->>Ctx: NEW: cleanupByTarget(targetId) & clear session listeners
Loading

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

@browserbase browserbase deleted a comment from cubic-dev-ai bot Feb 6, 2026
@browserbase browserbase deleted a comment from cubic-dev-ai bot Feb 6, 2026
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 6, 2026

Review Summary

This PR successfully fixes context.addInitScript() iframe issues by ensuring init scripts run in OOPIFs and SPIF iframes. The approach is sound—pausing targets before script execution and propagating auto-attach properly solves the root cause.


Key Changes Review

1. Init Script Registration with runImmediately: true

The addition of runImmediately: true to Page.addScriptToEvaluateOnNewDocument ensures scripts execute before page code:

session.send("Page.addScriptToEvaluateOnNewDocument", {
  source,
  runImmediately: true,
})

This is the correct fix for timing issues.

2. Auto-Attach Propagation with waitForDebuggerOnStart

Setting Target.setAutoAttach with waitForDebuggerOnStart: true on each session ensures OOPIFs pause before execution:

session.send("Target.setAutoAttach", {
  autoAttach: true,
  waitForDebuggerOnStart: true,
  flatten: true,
})

This cascades properly to nested frames and is the right approach.

3. Resume After Script Registration

Moving resume() to the end of the promise array (context.ts:500) prevents the race condition where targets execute before scripts are registered. This is critical.

4. Per-Session Target Listeners

The new installTargetSessionListeners() method properly tracks event listeners per session, preventing duplicate handlers and ensuring nested iframes are handled correctly.

5. Removal of Target.targetCreated Fallback

Removing the explicit attach logic in the Target.targetCreated handler (context.ts:375-387) is correct since auto-attach now handles all cases reliably.


Potential Issues

⚠️ Error Swallowing in send() Helper

The send() helper (context.ts:463-464) swallows all errors:

const send = (method: string, params?: object) =>
  session.send(method, params).catch(() => {});

Issue: Critical failures like Page.enable or Runtime.enable failing will be silently ignored, and the code continues as if they succeeded.

Suggestion: At minimum, log these errors:

const send = (method: string, params?: object) =>
  session.send(method, params).catch((err) => {
    v3Logger.warn(`Session ${sessionId}: ${method} failed`, { error: err });
  });

Or better yet, only swallow errors for non-critical operations and let critical ones fail properly.


⚠️ Piercer Pre-Registration Timing

The piercer registration (context.ts:489-498) sets piercerPreRegistered = true in a .then() callback, but all promises are awaited with Promise.allSettled() afterward.

Potential Issue: If the piercer registration fails, piercerPreRegistered remains false, and the code falls back to ensurePiercer() later. However, the comment at context.ts:512-518 suggests this is intentional.

Clarification Needed: Is the fallback to ensurePiercer() at context.ts:523 intentional for all cases, or only when pre-registration fails? The current logic seems correct, but the dual registration path could be confusing for future maintainers.


⚠️ waitForChildFrame() Timeout Handling

The new waitForChildFrame() helper (iframe-ctx-addInitScript.spec.ts:11-32) polls for frames with a 10s timeout. If a frame never appears or takes longer than 10s, tests will fail with "Timed out waiting for child frame to load".

Question: Is 10s sufficient for all test environments (CI, slow machines)? The old tests used a fixed 1s wait, which was error-prone but predictable.

Suggestion: Consider making the timeout configurable or using a more robust frame detection mechanism (e.g., listening for frameattached events).


Test Changes Review

The test changes are a significant improvement:

  • ✅ Removed arbitrary setTimeout(1000) waits
  • ✅ Added proper waitForChildFrame() polling
  • ✅ Removed unnecessary expect(iframe).toBeDefined() checks (TypeScript already ensures non-null)
  • ✅ Unskipped all 6 iframe tests

The tests now properly validate the fix and are more reliable.


Minor Nits

  1. Unused sessionKey() method removed (context.ts:48-50) ✅ — Good cleanup.

  2. Cleanup in onDetachedFromTarget() (context.ts:633-635) ✅ — Properly removes session tracking to prevent memory leaks.

  3. Comment accuracy (context.ts:348) — The comment now accurately reflects that auto-attach handles all cases, not just as a "fallback."


Overall Assessment

The fix is solid and addresses the root cause of init script issues in iframes. The main concern is error handling—silently swallowing errors could make debugging future issues difficult. Otherwise, the logic is sound and the tests validate the fix well.

Recommendation: Add logging to the send() helper before merging.

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

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 3 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant CTX as V3Context
    participant CDP as CDP Session (Page/OOPIF)
    participant BROWSER as Browser Engine
    participant JS as Page Runtime

    Note over CTX,BROWSER: Target Lifecycle & Init Script Injection

    BROWSER->>CTX: Target.attachedToTarget (New Page or OOPIF)
    
    rect rgb(23, 37, 84)
    Note right of CTX: NEW: Bootstrap Session (initSession)
    
    CTX->>CDP: Page.enable / Runtime.enable
    
    CTX->>CDP: CHANGED: Target.setAutoAttach<br/>(autoAttach: true, waitForDebuggerOnStart: true)
    Note over CDP,BROWSER: Pauses execution of the new target immediately
    
    loop For each Init Script
        CTX->>CDP: CHANGED: Page.addScriptToEvaluateOnNewDocument<br/>(source, runImmediately: true)
    end

    CTX->>CDP: NEW: Page.addScriptToEvaluateOnNewDocument<br/>(piercerScript, runImmediately: true)
    
    CTX->>CDP: CHANGED: Runtime.runIfWaitingForDebugger
    Note over BROWSER,JS: Target Resumes
    end

    alt NEW: Piercer Pre-registered successfully
        CTX->>CTX: Mark session piercer as installed
    else Registration failed
        CTX->>JS: Fallback: installV3PiercerIntoSession (standard eval)
    end

    Note over CTX,BROWSER: OOPIF / Nested Iframe Flow

    BROWSER->>CDP: Target.attachedToTarget (Sub-target)
    Note over CDP: NEW: auto-attach cascade ensures<br/>nested session is also paused
    
    CDP-->>CTX: Emit attachedToTarget for nested session
    CTX->>CTX: NEW: installTargetSessionListeners(nestedSession)
    CTX->>CTX: Recursively execute Bootstrap Session (above)

    Note over CTX,JS: Result: Scripts run in all frames before page JS executes.

    deactivate CTX
    
    Note over CTX: NEW: Cleanup on Detach
    BROWSER->>CTX: Target.detachedFromTarget
    CTX->>CTX: Remove session listeners & tracking sets
Loading

@browserbase browserbase deleted a comment from cubic-dev-ai bot Feb 6, 2026
@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 6, 2026

@greptileai @cubic-dev-ai

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

@seanmcguire12 seanmcguire12 marked this pull request as ready for review February 6, 2026 00:45
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 3 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant CTX as V3Context
    participant CDP as CDP Session (Page/OOPIF)
    participant BROWSER as Browser Engine
    participant JS as Page Runtime

    Note over CTX,BROWSER: Target Lifecycle & Init Script Injection

    BROWSER->>CTX: Target.attachedToTarget (New Page or OOPIF)
    
    rect rgb(23, 37, 84)
    Note right of CTX: NEW: Bootstrap Session (initSession)
    
    CTX->>CDP: Page.enable / Runtime.enable
    
    CTX->>CDP: CHANGED: Target.setAutoAttach<br/>(autoAttach: true, waitForDebuggerOnStart: true)
    Note over CDP,BROWSER: Pauses execution of the new target immediately
    
    loop For each Init Script
        CTX->>CDP: CHANGED: Page.addScriptToEvaluateOnNewDocument<br/>(source, runImmediately: true)
    end

    CTX->>CDP: NEW: Page.addScriptToEvaluateOnNewDocument<br/>(piercerScript, runImmediately: true)
    
    CTX->>CDP: CHANGED: Runtime.runIfWaitingForDebugger
    Note over BROWSER,JS: Target Resumes
    end

    alt NEW: Piercer Pre-registered successfully
        CTX->>CTX: Mark session piercer as installed
    else Registration failed
        CTX->>JS: Fallback: installV3PiercerIntoSession (standard eval)
    end

    Note over CTX,BROWSER: OOPIF / Nested Iframe Flow

    BROWSER->>CDP: Target.attachedToTarget (Sub-target)
    Note over CDP: NEW: auto-attach cascade ensures<br/>nested session is also paused
    
    CDP-->>CTX: Emit attachedToTarget for nested session
    CTX->>CTX: NEW: installTargetSessionListeners(nestedSession)
    CTX->>CTX: Recursively execute Bootstrap Session (above)

    Note over CTX,JS: Result: Scripts run in all frames before page JS executes.

    deactivate CTX
    
    Note over CTX: NEW: Cleanup on Detach
    BROWSER->>CTX: Target.detachedFromTarget
    CTX->>CTX: Remove session listeners & tracking sets
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, 2 comments

Edit Code Review Agent Settings | Greptile

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 3 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant CTX as V3Context
    participant CDP as CDP Session (Page/OOPIF)
    participant BROWSER as Browser Engine
    participant JS as Page Runtime

    Note over CTX,BROWSER: Target Lifecycle & Init Script Injection

    BROWSER->>CTX: Target.attachedToTarget (New Page or OOPIF)
    
    rect rgb(23, 37, 84)
    Note right of CTX: NEW: Bootstrap Session (initSession)
    
    CTX->>CDP: Page.enable / Runtime.enable
    
    CTX->>CDP: CHANGED: Target.setAutoAttach<br/>(autoAttach: true, waitForDebuggerOnStart: true)
    Note over CDP,BROWSER: Pauses execution of the new target immediately
    
    loop For each Init Script
        CTX->>CDP: CHANGED: Page.addScriptToEvaluateOnNewDocument<br/>(source, runImmediately: true)
    end

    CTX->>CDP: NEW: Page.addScriptToEvaluateOnNewDocument<br/>(piercerScript, runImmediately: true)
    
    CTX->>CDP: CHANGED: Runtime.runIfWaitingForDebugger
    Note over BROWSER,JS: Target Resumes
    end

    alt NEW: Piercer Pre-registered successfully
        CTX->>CTX: Mark session piercer as installed
    else Registration failed
        CTX->>JS: Fallback: installV3PiercerIntoSession (standard eval)
    end

    Note over CTX,BROWSER: OOPIF / Nested Iframe Flow

    BROWSER->>CDP: Target.attachedToTarget (Sub-target)
    Note over CDP: NEW: auto-attach cascade ensures<br/>nested session is also paused
    
    CDP-->>CTX: Emit attachedToTarget for nested session
    CTX->>CTX: NEW: installTargetSessionListeners(nestedSession)
    CTX->>CTX: Recursively execute Bootstrap Session (above)

    Note over CTX,JS: Result: Scripts run in all frames before page JS executes.

    deactivate CTX
    
    Note over CTX: NEW: Cleanup on Detach
    BROWSER->>CTX: Target.detachedFromTarget
    CTX->>CTX: Remove session listeners & tracking sets
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.

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +61 to 85
private installTargetSessionListeners(session: CDPSessionLike): void {
const sessionId = session.id;
if (!sessionId) return;
if (this._targetSessionListeners.has(sessionId)) return;
this._targetSessionListeners.add(sessionId);

session.on<Protocol.Target.AttachedToTargetEvent>(
"Target.attachedToTarget",
(evt) => {
void this.onAttachedToTarget(evt.targetInfo, evt.sessionId);
},
);
session.on<Protocol.Target.DetachedFromTargetEvent>(
"Target.detachedFromTarget",
(evt) => {
this.onDetachedFromTarget(evt.sessionId, evt.targetId ?? null);
},
);
session.on<Protocol.Target.TargetDestroyedEvent>(
"Target.targetDestroyed",
(evt) => {
this.cleanupByTarget(evt.targetId);
},
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Listeners never removed

installTargetSessionListeners() registers 3 Target.* event handlers on each child session, but onDetachedFromTarget() only deletes the sessionId from _targetSessionListeners and does not remove the handlers. If Chrome reuses the same session object (or if detach/reattach happens in a long-lived context), this can lead to duplicated handler execution and retained references. Store the bound handler functions per sessionId and call session.off(...) (or the equivalent) during detach/cleanup.

Also appears at packages/core/lib/v3/understudy/context.ts:634-636 where only the set entries are deleted.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/lib/v3/understudy/context.ts
Line: 61:85

Comment:
**Listeners never removed**

`installTargetSessionListeners()` registers 3 `Target.*` event handlers on each child session, but `onDetachedFromTarget()` only deletes the sessionId from `_targetSessionListeners` and does not remove the handlers. If Chrome reuses the same session object (or if detach/reattach happens in a long-lived context), this can lead to duplicated handler execution and retained references. Store the bound handler functions per `sessionId` and call `session.off(...)` (or the equivalent) during detach/cleanup.

Also appears at `packages/core/lib/v3/understudy/context.ts:634-636` where only the set entries are deleted.

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

Comment on lines 164 to 172
private async ensurePiercer(session: CDPSessionLike): Promise<boolean> {
const key = this.sessionKey(session);
if (this._piercerInstalled.has(key)) return true;
const id = session.id ?? "";
if (this._piercerInstalled.has(id)) return true;

const installed = await installV3PiercerIntoSession(session);
if (installed) {
this._piercerInstalled.add(key);
this._piercerInstalled.add(id);
}
return installed;
Copy link
Contributor

Choose a reason for hiding this comment

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

Piercer short-circuit bug

ensurePiercer() uses const id = session.id ?? "" and short-circuits when _piercerInstalled contains that id. For the root session (where session.id is commonly undefined), this uses the empty string key, so once any root call marks it installed, all future root-session checks will short-circuit even if the piercer was never actually installed (or was cleared incorrectly). Use a consistent non-empty key for root (e.g. session.id ?? "root") and make sure the same key shape is used everywhere _piercerInstalled is add/has/delete (including the pre-register path in onAttachedToTarget).

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/lib/v3/understudy/context.ts
Line: 164:172

Comment:
**Piercer short-circuit bug**

`ensurePiercer()` uses `const id = session.id ?? ""` and short-circuits when `_piercerInstalled` contains that id. For the root session (where `session.id` is commonly `undefined`), this uses the empty string key, so once any root call marks it installed, *all* future root-session checks will short-circuit even if the piercer was never actually installed (or was cleared incorrectly). Use a consistent non-empty key for root (e.g. `session.id ?? "root"`) and make sure the same key shape is used everywhere `_piercerInstalled` is `add`/`has`/`delete` (including the pre-register path in `onAttachedToTarget`).

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

Comment on lines +470 to +474
.send("Target.setAutoAttach", {
autoAttach: true,
waitForDebuggerOnStart: true,
flatten: true,
})
Copy link
Member

Choose a reason for hiding this comment

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

do you want to exclude service workers here, pausing bg workers doesnt always work well

@seanmcguire12 seanmcguire12 merged commit b27c04d into main Feb 6, 2026
49 checks passed
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.

2 participants