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.
Summary
Calling
app.interact(scrape_id, code=..., language="python")on a scrape-bound session fails the majority of the time withplaywright._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 viabrowser_execute. Node (default) and Bash on the same scrape-bound session succeed every time.Environment
firecrawl-py==4.25.2Repro
Observed (3 consecutive runs)
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 — sameTargetClosedError, just ongotoinstead oftitle.Sandbox-side traceback
The error returned by the API is the sandbox's Python traceback:
So: a
pageobject 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:
So the bug is specific to how scrape-bound
/v2/scrape/{scrapeId}/interactwires itspageinto 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 apage.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
pageinto the Node and Bash runtimes for scrape-bound interact (presumably the CDP attach + Playwrightconnect_over_cdpstep) 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.