Conversation
🦋 Changeset detectedLatest commit: d5e86da The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 OverviewGreptile SummaryThis PR fixes worker crashes caused by unhandled CDP session detachment rejections when a Playwright-connected page closes during inflight CDP calls. Key changes:
Root cause: The Solution: By returning the original promise and adding internal catch handlers at the promise creation site, the promise chain maintains proper error handling while preserving the same external API semantics. Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Caller
participant Page.sendCDP
participant CdpSession
participant CdpConnection
participant WebSocket
participant Browser
Note over Caller,Browser: Before Fix: async wrapper creates unhandled rejection
Caller->>Page.sendCDP: sendCDP("Runtime.evaluate", params)
Note over Page.sendCDP: OLD: async wrapper creates new Promise
Page.sendCDP->>CdpSession: send(method, params)
CdpSession->>CdpConnection: _sendViaSession(sessionId, method, params)
CdpConnection->>CdpConnection: Create promise p, store in inflight map
CdpConnection->>WebSocket: send(JSON.stringify(payload))
CdpConnection-->>Page.sendCDP: Return promise p
Note over Page.sendCDP: Wrapper creates outer promise
Page.sendCDP-->>Caller: Return wrapped promise
Browser->>Browser: Page closes
Browser->>WebSocket: Target.detachedFromTarget event
WebSocket->>CdpConnection: onMessage(detach event)
CdpConnection->>CdpConnection: Reject inflight promise p
Note over CdpConnection,Caller: Inner promise rejected before outer awaited
Note over Caller: UNHANDLED REJECTION!
Note over Caller,Browser: After Fix: return original promise
Caller->>Page.sendCDP: sendCDP("Runtime.evaluate", params)
Note over Page.sendCDP: NEW: returns original promise directly
Page.sendCDP->>CdpSession: send(method, params)
CdpSession->>CdpConnection: _sendViaSession(sessionId, method, params)
CdpConnection->>CdpConnection: Create promise p, store in inflight map
CdpConnection->>CdpConnection: void p.catch(() => {})
Note over CdpConnection: Promise already has internal handler
CdpConnection->>WebSocket: send(JSON.stringify(payload))
CdpConnection-->>Caller: Return promise p (same reference)
Browser->>Browser: Page closes
Browser->>WebSocket: Target.detachedFromTarget event
WebSocket->>CdpConnection: onMessage(detach event)
CdpConnection->>CdpConnection: Reject inflight promise p
Note over CdpConnection: Internal catch handler prevents unhandled rejection
Note over Caller: Caller receives rejection when they await
Note over Caller: No unhandled rejection!
|
There was a problem hiding this comment.
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 Caller as Test/Application
participant Page as Stagehand Page
participant CDP as CdpConnection
participant WS as Browser WebSocket
Note over Caller,WS: CDP Command Execution Flow
Caller->>>Page: sendCDP(method, params)
Page->>>CDP: CHANGED: mainSession.send(method, params)
Note right of Page: Now returns promise directly (removed async)
CDP->>CDP: Create pending promise (p)
rect rgb(23, 37, 84)
Note right of CDP: NEW: Internal Guard
CDP->>CDP: void p.catch(() => {})
Note right of CDP: Prevents global unhandledRejection
end
CDP->>WS: Send JSON payload
CDP-->>Page: Return promise (p)
Page-->>Caller: Return promise (p)
alt Happy Path
WS-->>CDP: Response received
CDP->>Caller: Resolve promise (p)
else Unhappy Path (Session Detached)
Note over Caller,WS: Page/Target closes while promise is inflight
WS-->>CDP: Socket closed / Session detached
CDP->>CDP: Reject all pending promises
alt Node.js Runtime Check
CDP-->>Caller: CHANGED: Rejection propagates via original promise
Note right of Caller: Caller receives "CDP session detached" error
else Global Error Handling
Note over CDP: Internal catch guard prevents process crash
end
end
why
unhandledRejection: Error("CDP session detached")when a Playwright‑connected page closed while Stagehand had inflight CDP calls.Page.sendCDPwas async, which wraps the underlying CDP promise. The detach rejection hit the inner promise before the outer wrapper was awaited, so Node treated it as unhandled.what changed
test plan
Summary by cubic
Fixes worker crashes from unhandled CDP session detaches when a page closes during in‑flight calls. Addresses STG-1240 by returning the original sendCDP promise and guarding internal CDP promises.
Written for commit d5e86da. Summary will update on new commits. Review in cubic