Introducing web search monitors

Playwright MCP browser_evaluate Hangs? The CSP Sandbox Bug Explained

TL;DR

  • If your agent navigates to a page, the navigation succeeds, and then browser_evaluate, browser_click, browser_press_key, or browser_run_code_unsafe never returns a result, check the page's response headers for Content-Security-Policy: ... sandbox. Raw files on raw.githubusercontent.com send exactly that header.
  • The cause is a short "settle" wait that Playwright MCP runs after each action. It waits on a setTimeout inside the page, and on these pages the timer never fires, so the tool call never completes. A paused Playwright clock triggers the same wait.
  • The bug is tracked in microsoft/playwright#43752. A fix, PR #43767, was approved on October 31, 2026 but was not merged when we checked on November 1, 2026.
  • The latest release, @playwright/mcp 0.0.84 (October 31, 2026), still ships the unbounded wait. We reproduced the hang on it with Chromium. Firefox returned normally for the CSP case, and --timeout-settle 0 did not help.
  • If you only need the page's content, fetch it outside the stuck browser session. A plain HTTP request or a scraping tool returns the text without depending on page timers.

Last checked: November 1, 2026. We will update this post when the fix ships in a release.

Does this match your failure?

Hangs in browser agents have several unrelated causes. Find where progress stops before you assume it is this bug.

Where it stopsLikely causeFirst thing to check
No tool has ever returned, even the first navigationMCP startup or transport, or the separate workspace-roots stall fixed in 0.0.84Server logs, launch errors, --ignore-roots (see below)
browser_navigate returns, then a trivial browser_evaluate never doesThis bug: the page cannot run timersResponse headers for a CSP sandbox directive
You called page.clock.pauseAt() or similar, then tools stop returningThis bug: paused clockWhether the clock is still paused
Only browser_click stalls, on one elementUsually actionability: the element is covered, disabled, or behind a modalThe page snapshot and any open dialogs
Everything is slow but eventually returnsNetwork or heavy pages, not a hangTiming with a simple fixture

The quickest test: run browser_evaluate with () => 1 right after navigation. On an affected page, even that call does not come back.

The 0.0.84 release notes describe a different stall: the first tool call hangs when a client advertises workspace roots but cannot answer roots/list. That one is fixed with --ignore-roots, ignoreRoots: true, or PLAYWRIGHT_MCP_IGNORE_ROOTS=true. Those settings do nothing for the timer bug in this post.

What is waiting on what

Playwright MCP wraps actions such as evaluate, click, and key presses in a completion helper. After the action itself finishes, the helper waits a short settle interval (500 ms by default) so that any navigation or network requests the action triggered have a chance to start. Only then does it build the tool result.

In the published 0.0.84 package, that settle wait is implemented by evaluating a promise inside the page:

JavaScript
await this.page.evaluate((ms) => new Promise((f) => setTimeout(f, ms)), time).catch(() => {});

There are two ways this promise can stay pending forever:

  1. A CSP sandbox response header. The sandbox directive applies iframe-style restrictions to the document, including blocking scripts unless allow-scripts is present. In our Chromium test the action itself ran, but the settle timer never fired on this document.
  2. A paused Playwright clock. Playwright's clock API can pause time for timers in the page. While it is paused, a setTimeout callback does not run, so the settle wait does not resolve.

The .catch(() => {}) handles a rejected promise, but a promise that never settles is not rejected. Nothing on the Node side puts a deadline on the wait, so the tool call has nothing that ends it.

The proposed fix in PR #43767 keeps the page-side wait but races it against a deadline measured in Node: the settle interval plus five seconds. A deadline based on the server's own clock does not depend on the page being able to run timers.

Our reproduction on @playwright/mcp 0.0.84

We ran this on November 1, 2026 on macOS (Apple silicon), headless, with @playwright/mcp 0.0.84 (which depends on playwright-core 1.65.0-alpha-2026-10-30). Browsers: Chromium headless shell 157.0.8103.15 (r1252) and Firefox 158.0 (r1569). Each case ran in a fresh --isolated session, driven over stdio by a small JSON-RPC client that stopped waiting after 30 seconds.

The fixture is a local server with two endpoints that differ only in the CSP header. The header is the same one raw.githubusercontent.com returns, which we confirmed with curl -I on the same day.

JavaScript
import http from 'node:http';
 
http
	.createServer((req, res) => {
		const headers = { 'content-type': 'text/plain' };
		if (req.url === '/sandboxed') {
			headers['content-security-policy'] = "default-src 'none'; style-src 'unsafe-inline'; sandbox";
		}
		res.writeHead(200, headers);
		res.end('MCP timer fixture');
	})
	.listen(8787, '127.0.0.1');

For each endpoint we called browser_navigate, then this evaluation:

JSON
{
  "name": "browser_evaluate",
  "arguments": { "function": "() => document.body.textContent" }
}

Results:

BrowserEndpointbrowser_navigatebrowser_evaluate
Chromium/plainReturnedReturned "MCP timer fixture" in about 0.5 s
Chromium/sandboxedReturnedNo response after 30 s
Firefox/plainReturnedReturned in about 0.5 s
Firefox/sandboxedReturnedReturned "MCP timer fixture" in about 0.5 s

The roughly 500 ms on successful evaluations is the default settle interval. We also tried two things that people reach for first:

  • --timeout-settle 0 still hung on Chromium. Shortening the settle interval to zero still schedules a page timer, and that timer still never fires.
  • Moving on after a client-side deadline worked in our setup. After our client stopped waiting at 15 seconds, it sent a new browser_navigate to /plain and a new browser_evaluate in the same session. Both returned normally. This shows the session was still usable for new calls. It does not show that the original call was cancelled on the server.

What we did not test: WebKit, branded Chrome, the paused-clock trigger, headed mode, Linux, Windows, and the patch from PR #43767. The upstream automated triage on the issue reports that Chrome, Chromium, and WebKit hung on the CSP case while Firefox passed, and that all three engines hung on the paused-clock case. That triage ran on Ubuntu 24.04 against a main-branch build. Our results agree with it for Chromium and Firefox.

What to do now

There is no released version with the fix as of November 1, 2026. These are your options, from most to least general.

1. Check whether the fix has shipped. Look at PR #43767 for a merge, then at the Playwright MCP releases for a version that includes it. Approval is not a merge, and a merge is not a release. Once a release includes it, upgrade and rerun the fixture above to confirm the evaluate call returns.

2. Give your MCP client a deadline. A per-call timeout on the client side will not make the tool succeed, but it turns an indefinite hang into a failure your agent can handle. In our test, the session accepted new calls afterward. Navigate away from the affected page before you continue.

3. Use Firefox for CSP-sandboxed pages. Starting the server with --browser firefox avoided the CSP case in our test and in the upstream triage. It does not help with a paused clock, which upstream reports hangs on Firefox too.

4. Do not pause the clock across MCP actions. If you use page.clock through browser_run_code_unsafe, resume time before your next MCP tool call. If you only need a stable date, page.clock.setFixedTime() fixes Date.now() while timers keep running. The clock docs explain the difference between setting a fixed time and pausing.

5. Do not use the browser when you only need the text. Many of the pages that trigger this bug are raw files: READMEs, JSON, logs. A browser adds nothing for those. See the next section.

Do not strip security headers from third-party pages to work around this, and do not weaken your own site's CSP just to accommodate an automation client.

When you only need the page's content

The original report on the issue came from an agent opening a raw GitHub README. If the agent's goal is to read that file, a request that never runs the page's timers avoids the hang entirely. curl is enough for a raw file you already know the URL of.

If your agent needs this across many sites, including pages that do need JavaScript rendering, a scraping tool is a better fit. The Context.dev MCP server gives agents web search and scraping as separate tools from the browser. We sent the same raw README URL through Context.dev's Scrape endpoint on November 1, 2026 and got the README back as Markdown:

Shell
curl --request POST "https://api.context.dev/v1/web/scrape" \
  --header "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "url": "https://raw.githubusercontent.com/microsoft/playwright/main/README.md",
    "formats": { "markdown": true }
  }'

This is about task boundaries, not a fix for Playwright. A scrape returns content. It does not keep your agent's logged-in browser session or click through a flow inside that session. For those tasks you still need Playwright MCP, and options 1 to 4 above. For more on splitting browser automation from content fetching, see how to set up Context.dev MCP in Cursor and Claude and our roundup of MCP servers for web scraping.

FAQ

Why does browser_evaluate hang on raw.githubusercontent.com?

Raw GitHub files are served with Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox. After evaluating your function, Playwright MCP waits on a setTimeout inside the page, and on this sandboxed document the timer never fires in Chromium. The tool call waits on it forever.

Is this fixed in @playwright/mcp 0.0.84?

No. We inspected the published 0.0.84 package on November 1, 2026: its settle wait still evaluates a page-side setTimeout with no deadline, and we reproduced the hang on it. The fix is in PR #43767, which was approved but not merged at that time.

Does it happen in Firefox?

For the CSP sandbox case, no: Firefox returned normally in our test and in the upstream triage. For the paused-clock case, upstream reports that Firefox hangs too.

Will increasing a timeout help?

Not on the server. --timeout-settle 0 still hung in our test, and --timeout-action and --timeout-navigation do not cover the settle wait. A timeout in your MCP client helps only in that it stops waiting and lets your agent move on.

Which tools are affected?

The issue lists browser_evaluate, browser_click, browser_press_key, and browser_run_code_unsafe, which share the same completion path. We tested browser_evaluate. browser_navigate returned normally on the sandboxed page.

Is this the same as the first-tool-call stall fixed in 0.0.84?

No. A first tool call that never returns, before any page loads, matches the workspace-roots stall described in the 0.0.84 release notes. That is fixed with --ignore-roots. The timer bug starts after a successful navigation.

Conclusion

When a Playwright MCP action never returns after a successful navigation, check for a CSP sandbox header or a paused clock. The tool is waiting on a page timer that cannot run. Until PR #43767 ships in a release, put a deadline on the client side, use Firefox for sandboxed pages, keep the clock running across actions, and fetch plain content without a browser when that is all you need.

Start building with live web context.

Scrape, search, and research the web with one API. Bring the results straight into your product.

1,000free credits

Every month. No credit card required.

Start for freeRead the docs