Skip to content

Commit 041b92a

Browse files
authored
fix(stagehand): Inject --no-sandbox into Stagehand's Chromium launch when sandbox is disabled (#1906)
## Summary The scheduled e2e tests for the Stagehand template have failed for several days. After PR #1900 fixed the `OPENAI_API_KEY` propagation, the next layer of failure surfaced — every Stagehand variant in [today's run](https://github.com/apify/crawlee-python/actions/runs/26136278735) failed identically on the Apify platform with: ``` stagehand.InternalServerError: Error code: 500 - {'success': False, 'message': 'connect ECONNREFUSED 127.0.0.1:<port>'} ``` ## Root cause Reproduced locally inside the `apify/actor-python-playwright:3.13` image: Stagehand's `BrowserLaunchOptions` accepts a `chromium_sandbox` field, but the field is **not propagated** to the underlying Chromium launch. When the actor runs as root (which it does on Apify), Chromium silently refuses to start because the setuid sandbox can't initialise — and Stagehand's local SEA server then hits `ECONNREFUSED` when it tries to connect to the missing Chromium CDP port. The regular `PlaywrightCrawler` is unaffected because Playwright's Python binding *does* translate `chromium_sandbox=False` into `--no-sandbox`. ## Fix In `StagehandBrowserPlugin`, when the sandbox is disabled (`config.disable_browser_sandbox=True`, which Apify auto-sets), append `'--no-sandbox'` to `browser_launch_options['args']` as an explicit workaround. Verified end-to-end in the same actor base image — the crawler now completes requests successfully. The `apify push` hang from the same scheduled run is a separate flake and is addressed in #1905.
1 parent feec30d commit 041b92a

2 files changed

Lines changed: 32 additions & 0 deletions

File tree

src/crawlee/browsers/_stagehand_browser_plugin.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ def __init__(
8888
if user_data_dir is not None:
8989
self._browser_launch_options['user_data_dir'] = str(user_data_dir)
9090

91+
# Stagehand's BrowserLaunchOptions accept `chromium_sandbox` but the field is not propagated
92+
# to the underlying Chromium launch, so containers running as root hit ECONNREFUSED on the CDP port.
93+
# Inject `--no-sandbox` explicitly when the sandbox is disabled.
94+
if not self._browser_launch_options['chromium_sandbox']:
95+
args = list(self._browser_launch_options.get('args') or [])
96+
if '--no-sandbox' not in args:
97+
args.append('--no-sandbox')
98+
self._browser_launch_options['args'] = args
99+
91100
# Parameters for AsyncStagehand.
92101
self._stagehand_init_params: dict[str, Any] = {
93102
'server': 'local' if is_local else 'remote',

tests/unit/browsers/test_stagehand_browser_plugin.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,29 @@ def test_order_priority_of_implicit_options() -> None:
6464
assert plugin.browser_launch_options['viewport'] == {'width': 1280, 'height': 720}
6565

6666

67+
def test_no_sandbox_added_when_sandbox_disabled() -> None:
68+
# The autouse fixture in conftest sets CRAWLEE_DISABLE_BROWSER_SANDBOX=true.
69+
plugin = StagehandBrowserPlugin()
70+
71+
assert plugin.browser_launch_options['chromium_sandbox'] is False
72+
assert '--no-sandbox' in plugin.browser_launch_options['args']
73+
74+
75+
def test_no_sandbox_not_added_when_sandbox_enabled(monkeypatch: pytest.MonkeyPatch) -> None:
76+
monkeypatch.setenv('CRAWLEE_DISABLE_BROWSER_SANDBOX', 'false')
77+
plugin = StagehandBrowserPlugin()
78+
79+
assert plugin.browser_launch_options['chromium_sandbox'] is True
80+
assert 'args' not in plugin.browser_launch_options
81+
82+
83+
def test_no_sandbox_not_duplicated_when_already_in_args() -> None:
84+
plugin = StagehandBrowserPlugin(browser_launch_options={'args': ['--no-sandbox', '--disable-gpu']})
85+
86+
assert plugin.browser_launch_options['args'].count('--no-sandbox') == 1
87+
assert '--disable-gpu' in plugin.browser_launch_options['args']
88+
89+
6790
def test_stagehand_options_defaults_when_not_provided() -> None:
6891
plugin = StagehandBrowserPlugin()
6992

0 commit comments

Comments
 (0)