Skip to content

[Bug] playwright-cli: one discarded (Memory Saver) tab makes every command time out — tab header rendering awaits page.title() without timeout #41714

Description

@Emericen

Version

@playwright/cli 0.1.15 (bundled playwright-core 1.62.0-alpha-2026-06-29)

Steps to reproduce

Environment: Windows 11, Chrome 150.0.7871.101, attaching to a long-running real Chrome profile.

  1. Run Chrome with --remote-debugging-port=9222 --user-data-dir=<dedicated profile> and use it normally for a few days with several tabs open (Stripe/Azure/Vercel dashboards in my case).
  2. Chrome's Memory Saver discards/freezes one of the idle background tabs. (Likely forcible on demand via chrome://discards → "Urgent discard", though my occurrence was organic.)
  3. playwright-cli attach --cdp=http://localhost:9222 → succeeds.
  4. Run any command: playwright-cli tab-list, eval "location.href", snapshot, …

Expected behavior

Commands work, with the discarded tab either listed as-is (title from target info) or marked unresponsive.

Actual behavior

Every command fails after 30s with:

### Error
TimeoutError: Timeout 30000ms exceeded.

The daemon is fully wedged — even commands that don't touch the bad tab (e.g. eval on the current tab) fail, because every command's response renders the "Open tabs" / "Page" header sections.

Diagnosis

The discarded tab's renderer never answers renderer-bound CDP calls. Since #41128 (fix for #41093), connectOverCDP no longer hangs at connect time on such a tab — it reports the page "as-is". The result is a half-initialized Page whose url() returns "" and whose title() / evaluate() never resolve (they don't recover even on fresh connections, or after the renderer is revived via Target.activateTarget / Page.navigate; only closing the tab clears it).

The tools backend then hangs on it for every command: Tab.headerSnapshot() does

await this._raceAgainstModalStates(async () => {
  title = await this.page.title();   // no timeout, no catch
});

(packages/playwright-core/src/tools/backend/tab.ts, visible at lib/coreBundle.js:63606 in the published bundle) and response rendering awaits headerSnapshot() for all tabs on every command. One zombie tab ⇒ every command times out.

Probing each page with Promise.race([page.title(), timeout]) over a raw connectOverCDP connection pinpoints the culprit: five healthy tabs answer in ms; the discarded one (empty url()) hangs on both title() and evaluate('1+1'), while browser-side CDP on the same target (Target.getTargetInfo) still answers fine and even knows the real title/URL.

Suggested fix

Guard the per-tab header with a small timeout and/or .catch() (like collectConsoleMessages already does), falling back to TargetInfo.title/url from the browser session, and render the tab as e.g. (unresponsive). Related report of the same underlying condition in another CDP-attaching agent tool: vercel-labs/agent-browser#1036.

Workaround

Close and recreate the discarded tab via a browser-level CDP session (Target.closeTarget + Target.createTarget) — the CLI heals instantly.

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions