· Paul Crossland
A Stopped Agent Can Leave a Live Browser Behind
Fresh WebMCP and Playwright cancellation changes expose the gap between stopping a tool call and proving its browser is quiet.
An agent reaches its time budget, the user cancels the task, and the orchestration layer marks the run stopped. Ten minutes later, the browser page is still polling APIs, running timers, changing storage, and consuming CPU. A later job inherits the context. Its extraction is now shaped by work everyone assumed had ended.
This is not one cancellation bug. It is a distributed lifecycle problem: the caller, tool protocol, automation client, browser target, page application, and artifact writer can all stop at different times.
Fresh changes show the gap from three directions. A Puppeteer pull request opened August 20 and merged August 21 adds AbortSignal cancellation to WebMCP execution and tests cancellation both during execution and from an already-aborted signal. A Playwright issue opened August 18 reports a hang after directly cancelling a Python asyncio task during startup; the documentation change merged August 19 now calls cancellation during a Playwright call unsupported and undefined.
Meanwhile, an August 20 Chrome DevTools MCP feature request reports a long-lived server retaining an active page for hours after client activity stopped. The maintainer says the server remains attached until pages close or the server stops, and recommends environment-specific sidecar cleanup. Its CPU and memory figures are the reporter's measurements, not a vendor benchmark.
The thesis: agent cancellation is a protocol, not an exception; a fetch platform must separately prove cancellation intent, tool settlement, automation settlement, page quiescence, artifact finalization, and resource release before it reuses capacity or trusts the next session.
This is operational guidance for owned or properly authorized automation. Cancellation does not retract a request already sent, erase a rate limit, or turn a login, challenge, payment boundary, denial, or source-policy outcome into permission to retry another way.
The repeated angle to avoid
Earlier Better Fetch posts already covered cancellation sources, browser teardown, control-plane telemetry, MCP evidence planes, and browser-state checkpoints. The repeated article would say to log abort reasons and close pages.
The new surplus is an end-to-end completion contract. This week's evidence shows that cancellation support can exist at one layer while being undefined at the next, and that an apparently idle client can leave a deliberately active browser behind. The required proof is therefore not cancelled: true; it is convergence across every layer capable of continuing work.
Source map
Fresh primary evidence from the last seven days:
| Source | Date | Contribution |
|---|---|---|
| Puppeteer pull request 15365 | 2026-08-20 | Adds an optional abort signal to WebMCP tool execution, sends a cancellation command for the invocation, returns a Canceled result, and adds tests. It merged August 21 but is not by itself evidence that a particular released package contains it. |
| Playwright issue 42296 | 2026-08-18 | Reproduces a Python process hang after quickly cancelling an asyncio task around Playwright startup and records the TaskGroup consequence raised by the reporter. |
| Playwright pull request 42312 | 2026-08-19 | Documents direct cancellation during a Playwright call as unsupported and undefined, recommending protected ownership when an operation must outlive its caller. |
| Chrome DevTools MCP issue 2599 | 2026-08-20 | Documents the intentional absence of a universal idle lifecycle in a persistent server and an operator's reported park, recycle, and activity-lease pattern. It remains an open feature request. |
Older background is the general asynchronous cancellation pattern: a caller communicates intent to stop, but the receiver defines what was interrupted and how completion is reported. The synthesis below carries that distinction to browser quiescence.
Six milestones hidden by one cancelled flag
A useful run model separates these events:
- Intent recorded. A user, budget controller, policy guard, parent task, or shutdown process requests cancellation with a reason and deadline.
- Tool cancellation accepted. The relevant invocation exists and its protocol surface accepts the request. An already-finished or unknown invocation needs a different result.
- Tool result settled. The caller receives
Canceled,Completed,Failed, orUnknown. Puppeteer's fresh work makes this distinction explicit for WebMCP execution. - Automation operation settled. Navigation, evaluation, screenshot, trace, or browser startup has either completed through a supported path or remains owned by a supervisor. Playwright's Python guidance shows why cancelling the outer task cannot be assumed to settle the inner operation.
- Page became quiescent. No unowned target keeps polling, rendering, writing storage, opening tabs, or issuing requests for the cancelled task.
- Evidence and resources finalized. Required traces and logs have a bounded finalization verdict; pages, contexts, browser processes, temporary profiles, and leases are released or deliberately quarantined.
Only the first milestone is guaranteed when an orchestrator writes cancel_requested. Even a clean Canceled tool result proves the invocation's protocol outcome, not that a page's independent timers stopped. Conversely, force-killing a browser may stop page work while losing the evidence needed to explain which requests had already left.
Issue a cancellation receipt
Give each cancellation one structured receipt that joins the layers:
cancel_id, run, agent turn, MCP call, tool invocation, automation operation, browser context, target, and document generation;- requester class, normalized reason, request time, soft and hard deadlines, and policy outcome already observed;
- cancellation capability by layer: supported, unsupported, not applicable, or unknown;
- protocol acknowledgement and final tool status, including mismatched or missing invocation IDs;
- automation operation status and owner after the caller exits;
- target set before and after cancellation, with page, worker, download, trace, and screencast activity counts;
- in-flight request count, last request completion, storage-mutation marker, and owned-fixture server receipt where available;
- artifact flush, context close, process exit, temporary-state cleanup, and worker reuse results;
- final verdict:
quiescent,settled_not_quiescent,forced_and_finalized,forced_evidence_partial,policy_stop, orunknown.
Do not put tool arguments, cookies, credentials, private page content, or authenticated URLs into broad telemetry. Identifiers, origin classes, counts, timings, reason codes, and restricted artifact references are usually sufficient.
Use a bounded shutdown ladder
A safe lifecycle sequence is progressive rather than one blind kill:
- Stop admitting dependent actions and suppress publication from the cancelled generation.
- Send the narrowest supported cancellation to the exact invocation or operation. Do not manufacture success when that surface is unsupported.
- Wait within a small budget for the tool and automation operation to settle. If a library requires an inner operation to outlive its caller, transfer ownership to a supervised task rather than abandoning it.
- Revoke task-scoped leases and wait for known page work to become quiet. Long-running traces or screencasts may hold explicit leases, but every lease needs an owner and expiry.
- Finalize required evidence with its own deadline. A forced stop should produce
evidence_partial, not a success-shaped empty trace. - Close or park task-owned targets according to the approved context-reuse policy. If target ownership is ambiguous, quarantine the context instead of handing it to another tenant or job.
- Recycle the browser worker when quiescence or cleanup cannot be proved. Capacity loss is cheaper than cross-run state contamination.
A fixed idle timeout alone is weak. Quiet pages can still hold sensitive state, and legitimate traces can be silent for longer than a polling SPA. Combine activity with ownership, leases, task state, and a hard maximum lifetime.
An owned-fixture cancellation test
Build the canary on pages and services you control:
- Register a harmless WebMCP tool that completes after a delay. Cancel it before dispatch and during execution; require a correlated
Canceledresult and no accepted output. - Start a Playwright Python call inside the same supervision pattern used in production. Cancel the parent workload and prove the inner call has an owner, a deadline, and a settled result without hanging process shutdown.
- Load a fixture SPA with timers, API polling, a worker, storage writes, and a popup. End client activity without cleanup and verify the system detects continued page work rather than calling the run idle.
- Hold a trace activity lease, request cancellation, and confirm that soft cleanup waits while the hard deadline remains enforceable. Then release the lease and prove targets close or park.
- Record requests at the fixture server. After the quiescence receipt, require zero task-attributable requests; client-side counters alone cannot prove the network stopped.
- Cancel during artifact finalization and during context close. Distinguish a complete artifact, a bounded partial artifact, and an unknown cleanup state.
- Return a login, challenge,
403,429, or payment boundary before cancellation. Preserve that outcome and assert that shutdown does not trigger a new identity, route, session, or retry strategy. - Repeat against the exact released automation and browser builds in use. Merged main-branch support and documentation changes are not deployment guarantees.
The operator decision rule
When a cancelled agent run appears to be consuming resources or affecting later data, find the last milestone with positive evidence. If the tool never settled, keep its output ineligible. If the tool settled but automation did not, supervise or terminate the owned operation without reusing its context. If automation settled but the page remains active, close or park the task-owned target. If quiescence is unknown, quarantine and recycle. If artifacts did not finalize, label dependent results partial rather than rebuilding a success from the agent transcript.
An agent stopping is a control-plane event. A browser becoming quiet is an observed system state. Treat the distance between them as a first-class lifecycle, and cancelled work stops being an invisible source of traffic, cost, state contamination, and false fetch evidence.