Skip to content

[Bug] /interact with language="python" flakily fails with TargetClosedError on scrape-bound sessions #3498

@BexTuychiev

Description

@BexTuychiev

Summary

Calling app.interact(scrape_id, code=..., language="python") on a scrape-bound session fails the majority of the time with playwright._impl._errors.TargetClosedError: Page.title: Target page, context or browser has been closed, even on the very first interact call after the scrape, and even with a no-op snippet. The exact same Python code runs reliably on a standalone Browser Sandbox session via browser_execute. Node (default) and Bash on the same scrape-bound session succeed every time.

Environment

  • firecrawl-py==4.25.2
  • Python 3.12
  • macOS (Darwin 25.4.0)
  • Cloud Firecrawl (no self-host)

Repro

import os
from dotenv import load_dotenv
from firecrawl import Firecrawl

load_dotenv()
app = Firecrawl(api_key=os.getenv("FIRECRAWL_API_KEY"))

# Any static page works as the scrape target.
result = app.scrape("https://pypi.org/project/requests/", formats=["markdown"])
scrape_id = result.metadata.scrape_id

# Node — succeeds reliably.
node = app.interact(scrape_id, code="await page.title()")
print("node:", node.result, "exit:", node.exit_code)

# Bash — succeeds reliably.
bash = app.interact(scrape_id, code="agent-browser get url", language="bash")
print("bash:", bash.stdout, "exit:", bash.exit_code)

# Python — fails ~2/3 of the time, regardless of call order.
try:
    py = app.interact(
        scrape_id,
        code="print(await page.title())",
        language="python",
    )
    print("python:", py.stdout, "exit:", py.exit_code)
except Exception as e:
    print("python FAILED:", str(e).splitlines()[-1])

app.stop_interaction(scrape_id)

Observed (3 consecutive runs)

=== run 1 ===
node:     'requests · PyPI'    exit=0
bash:     'https://pypi.org/project/requests/'   exit=0
python:   FAILED — TargetClosedError: Page.title: Target page, context or browser has been closed

=== run 2 ===
node:     'requests · PyPI'    exit=0
bash:     'https://pypi.org/project/requests/'   exit=0
python:   'requests · PyPI'    exit=0   ← occasionally succeeds

=== run 3 ===
node:     'requests · PyPI'    exit=0
bash:     'https://pypi.org/project/requests/'   exit=0
python:   FAILED — TargetClosedError: Page.title: Target page, context or browser has been closed

I also tried isolating Python (no Node/Bash calls before it) — same flake rate. I tried adding await page.goto(...) at the top of the Python block — same TargetClosedError, just on goto instead of title.

Sandbox-side traceback

The error returned by the API is the sandbox's Python traceback:

playwright._impl._errors.TargetClosedError: Page.title: Target page, context or browser has been closed
  File "/usr/local/lib/python3.11/dist-packages/aioconsole/execute.py", line 145, in aexec
  File "/usr/local/lib/python3.11/dist-packages/playwright/async_api/_generated.py", line 9819, in title
  File "/usr/local/lib/python3.11/dist-packages/playwright/_impl/_page.py", line 824, in title
  File "/usr/local/lib/python3.11/dist-packages/playwright/_impl/_frame.py", line 882, in title
  File "/usr/local/lib/python3.11/dist-packages/playwright/_impl/_connection.py", line 69, in send

So: a page object is injected into the Python sandbox runtime, but it's pointing at a browser context that's already been closed (or was never properly attached) by the time the Python code runs.

Confirms it's not a client-side issue

Same Python snippet on a standalone Browser Sandbox session works every time:

session = app.browser()
py = app.browser_execute(
    session.id,
    code="""
await page.goto('https://pypi.org/project/requests/')
print(await page.title())
""",
    language="python",
)
print(py.stdout)  # "requests · PyPI" — every time
app.delete_browser(session.id)

So the bug is specific to how scrape-bound /v2/scrape/{scrapeId}/interact wires its page into the Python sub-runtime. Node and Bash get a working binding; Python doesn't (or gets one that's racing with cleanup).

Why this matters

The Interact docs at https://docs.firecrawl.dev/features/interact list language="python" as a first-class option with a page.click('#load-more') example, so users will reasonably expect it to work on scrape-bound sessions the same way Node and Bash do.

Suggested fix direction

Whatever wires page into the Node and Bash runtimes for scrape-bound interact (presumably the CDP attach + Playwright connect_over_cdp step) likely needs the same treatment for the Python runtime. The fact that the standalone Browser Sandbox path works for Python suggests the Python runtime itself is fine — the wiring at session-resume time isn't.

Happy to test a fix on this end.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    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