Skip to content

browser run: recover when an apparently open page belongs to a dead context #314

Description

@rishabhraj36

Summary

In Webcmd 0.7.1, raw browser run can retain and repeatedly reuse a Playwright page for which page.isClosed() is false, even though the underlying browser context/CDP connection is already closed.

The Session is created successfully and browser run can execute non-browser JavaScript, but every navigation fails with:

QuickJS promise rejected: Target page, context or browser has been closed

A new Session on the same Profile later works, so this is a transient local runtime/session lifecycle failure rather than a destination-site failure.

This was observed in a Pi benchmark run and caused the controller to exhaust its browser attempts without acquiring any page evidence.

Environment

  • Webcmd: 0.7.1 (/opt/homebrew/bin/webcmd)
  • Controller: @earendil-works/pi-coding-agent 0.80.6
  • OS: macOS
  • Profile: a long-lived shared benchmark Profile
  • Explicit opaque Session per benchmark task

Observed sequence

Using an explicit Session:

webcmd --profile benchmark --session <session-id> browser run --stdin <<'JS'
await page.goto('https://stackexchange.com/sites');
return { url: page.url(), title: await page.title() };
JS

failed repeatedly with the closed-target error. browser tabs returned [].

A diagnostic run against the same leased object then returned:

{
  "ok": true,
  "result": {
    "pageClosed": false,
    "contextPages": 1
  },
  "page": {
    "url": "about:blank"
  },
  "warnings": [
    {
      "code": "BROWSER_RUN_SNAPSHOT_FAILED",
      "message": "browserContext.newCDPSession: Target page, context or browser has been closed"
    }
  ]
}

Every subsequent page.goto(...) in that Session failed in the same way.

After the failed benchmark completed and cleaned up, creating a fresh explicit Session on the same benchmark Profile and navigating to https://example.com succeeded. This confirms the original failure was transient runtime state, not general connectivity.

Likely recovery gap

CloakSessionManager.getPage() treats an existing entry as healthy when page.isClosed() is false. Raw browser run then executes directly against that lease. A disconnected/dead context can therefore masquerade as an open page.

There is closed-context retry logic around page creation and navigatePage, but a closed-context failure thrown inside runBrowserProgram does not appear to invalidate the Profile runtime or retry with a fresh page/context. The failed snapshot is downgraded to a warning, so even the CDP failure does not repair the runtime before the next command.

This differs from #293, which covers initial adapter navigation, and is the raw browser run path. It is related to the lifecycle class previously discussed in #276.

Expected behavior

When browser run observes the closed-target signature from Playwright or snapshot CDP setup, Webcmd should:

  1. invalidate the dead Profile runtime;
  2. create a healthy replacement context/page;
  3. retry safely when no user-visible side effects occurred, or return a structured reconnect error when replay is unsafe;
  4. ensure the next command in the same Session does not receive the same dead lease.

At minimum, a failed run should evict the dead runtime so the next command can recover rather than repeatedly leasing the same invalid page.

Suggested regression coverage

  • Existing page reports isClosed() === false.
  • page.goto() inside browser run rejects with the closed-target signature.
  • context.newCDPSession(page) also rejects with the same signature.
  • The Profile runtime is invalidated.
  • The next raw browser command gets a replacement context/page and can navigate.
  • Recovery is bounded and does not blindly replay a program after possible side effects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions