EPIPE crash in Playwright Node driver when Google OAuth popup closes
Bug Report: Google OAuth Popup Crashes with EPIPE in Camoufox
Summary
When using Camoufox with Playwright's Python API, Google OAuth popup flows cause an EPIPE crash in the Playwright Node.js driver. The popup opens, but after the user clicks their Google account, the popup closes within ~2.5 seconds before the consent page can load, and the Playwright driver crashes with an unhandled EPIPE error on the PipeTransport.send() method.
This prevents any Google OAuth login flow (TikTok, etc.) from completing in headless or headed mode.
Environment
- Camoufox version: 0.5.6 (pip
camoufox[geoip]) - Camoufox browser: v152.0.4-beta.30 (aarch64-linux)
- Playwright version: 1.62.0 (bundled with Camoufox)
- Python: 3.11
- OS: Linux aarch64 (Armbian on Amlogic S905X)
- Node.js: v24.18.0
- Display: Xvfb (virtual framebuffer for headed mode)
Steps to Reproduce
from camoufox.sync_api import Camoufox
import time
with Camoufox(
headless=True, # or headless=False with DISPLAY=:99
os='windows',
humanize=True,
geoip=True,
main_world_eval=True,
persistent_context=True,
user_data_dir='/tmp/test-profile',
i_know_what_im_doing=True,
disable_coop=True
) as browser:
page = browser.new_page()
page.goto('https://www.tiktok.com/login', wait_until='networkidle', timeout=45000)
page.wait_for_timeout(15000)
# Click "Continue with Google" button
page.evaluate('''() => {
const els = document.querySelectorAll('div[data-e2e="channel-item"]');
for (const el of els) {
if ((el.textContent||'').includes('Google')) { el.click(); return; }
}
}''')
page.wait_for_timeout(10000)
# At this point, EPIPE crash occursExpected Behavior
- Google OAuth popup opens
- User selects account
- Consent page loads
- User clicks "Allow"/"Lanjutkan"
- Popup closes after token is sent via postMessage
- Parent page receives token and sets cookies
- No crash occurs
Actual Behavior
- Google OAuth popup opens ✅
- User account is clicked ✅
- Popup closes within ~2.5 seconds ❌
- Consent page never loads ❌
- EPIPE crash occurs in Playwright Node.js driver ❌
Error Stack Trace
node:events:487
throw er; // Unhandled 'error' event
^
Error: write EPIPE
at afterWriteDispatched (node:internal/stream_base_commons:159:15)
at writeGeneric (node:internal/streams/writable:389:12)
at _write (node:internal/streams/writable:330:10)
at Socket.Writable.write (node:internal/streams/writable:334:10)
at PipeTransport.send (coreBundle.js:34569:25)
at dispatcherConnection.onmessage (coreBundle.js:69266:59)
at DispatcherConnection.sendEvent (coreBundle.js:18641:14)
Emitted 'error' event on Socket instance at:
at emitErrorNT (node:internal/stream_base.destroy:157:8)
at emitErrorCloseNT (node:internal/stream_base.destroy:122:3)
at process.processTicksAndRejections (node:internal/process/task_queues:83:21) {
errno: -32,
syscall: 'write',
code: 'EPIPE'
}Root Cause Analysis
The EPIPE Crash
The crash occurs in PipeTransport.send() at coreBundle.js:34569. When the Google OAuth popup page is destroyed during navigation (account click → consent page redirect), Playwright's dispatcher tries to send a __dispose__ event to the Python client. However, the pipe between the Node.js driver and the Python process is already closed, causing the EPIPE error.
The error is emitted as an event on the Socket, not thrown by the write() call. This means:
try/catcharoundpipeWrite.write()does NOT catch it- The error is emitted asynchronously by Node.js's
Writablestream - The only way to handle it is via
socket.on('error', ...)handler
The Popup Lifecycle Issue
The popup closes within ~2.5 seconds after the account click. This is observed in the monitoring output:
0.0s: 3 pages: ['about:blank', 'https://www.tiktok.com/login', 'https://accounts.google.com/...']
2.5s: 2 pages: ['about:blank', 'https://www.tiktok.com/login']The Google popup page disappears before the consent page can fully load. This may be caused by:
- Google detecting the automated browser and closing the popup
- Camoufox's Juggler protocol not properly handling popup navigation
- A timing issue in how Camoufox manages popup page lifecycle
What We've Tried
| Approach | Result |
|---|---|
disable_coop=True |
EPIPE still crashes |
headless=False + Xvfb |
EPIPE still crashes |
| Mobile UA + viewport | Still popup flow, same crash |
EPIPE handler via NODE_OPTIONS |
Prevents crash but connection broken |
Patch PipeTransport.send() try/catch |
Doesn't catch (error is async event) |
Patch sendDispose() |
Doesn't catch (error is async event) |
Patch sendEvent() |
Doesn't catch (error is async event) |
Add socket.on('error') handler |
Prevents crash but consent still doesn't load |
Impact
- Critical: Any Google OAuth login flow via popup is completely broken
- Affects all sites that use Google OAuth with popup flow (TikTok, etc.)
- The issue is specific to Camoufox's Juggler protocol handling of popup pages
Additional Context
Related Issues
- #635: Juggler emits
Page.uncaughtErrorwithout location, crashing Playwright Node driver. This is related but different — our crash is inPipeTransport.send(), not in error handling. - Playwright #42390: Firefox launcher forces
-juggler-pipewhich breaks Camoufox on Windows. Related to Juggler compatibility. - Playwright #32081: Google login not working in Playwright Firefox. Confirms Google actively blocks automated browsers.
What Works
- Camoufox itself works perfectly for non-OAuth tasks
- Google login via MyAccount (direct navigation, not OAuth popup) works
- Persistent profiles work correctly
- Anti-detection features work (CreepJS, BrowserScan pass)
Possible Fixes
- Fix popup lifecycle in Juggler: When a popup page navigates, ensure the old page context is properly cleaned up before sending dispose events
- Add error handler to PipeTransport: Similar to
PipeTransport2which haspipeWrite.on("error", ...), add the same handler toPipeTransport - Fix the EPIPE in
send(): Thewrite()call should be wrapped in error handling that catches async stream errors - Investigate why popup closes in 2.5s: This may be a Google detection issue or a Juggler protocol issue
Labels
- bug
- playwright
- google-oauth
- popup
- EPIPE
- critical
Source: daijro/camoufox