Repository navigation
Description
What
A playwright-cli browser daemon started by an agent is never reclaimed. It outlives
its agent process, reparents to pid 1, and keeps a Chromium tree alive indefinitely. On one
long-running gateway host, measured directly:
- 19
cliDaemonprocesses, all reparented to pid 1, oldest 4 days 8 hours old - the CLI's registry knows only 5 of them (
default,lnkchk,oesweep,tg,tgv) - 14 of 19 have a
TMPDIRthat no longer exists - total resident set across the daemon trees: ~14.8 GB, median 0.81 GB per tree —
of which roughly 10.7 GB belongs to daemons no live session claims
Root cause
The CLI writes its control socket under TMPDIR. KiroCrew's agent_scratch points
TMPDIR at a per-process scratch directory, so a session's .session entry records
something like:
<scratch>/runtime-<id>/pw-<hash>/cli/<workspacehash>-<name>.sock
When that agent process dies, sweep_dead_scratch reclaims the directory. The daemon keeps
running, but its socket is gone, so:
- nothing can talk to it again — it is unreachable, not merely untracked;
- a later process gets a different
TMPDIR, so even reopening the same session name
cannot find or stop the old daemon. It just overwrites the registry entry and spawns a
new one.
Two independent facts confirm the mechanism: the 14 daemons whose TMPDIR is gone, and
three different scratch dirs on the same host each containing their own pw-<hash>/.
Why the obvious fix does not work
A reaper that releases via the CLI (playwright-cli -s=<name> close) cannot reclaim
these. close resolves the name through the registry to a socketPath and talks to it.
A stray has no .session entry at all — 5 entries for 19 daemons — so close answers
"not open" and the daemon survives. Such a reaper only ever reclaims a browser whose owner
is dead but whose registry entry and socket still resolve, which on this evidence is
close to never. It would walk past every byte of the 10.7 GB above.
This was implemented and then removed from #5952's PR for exactly this reason.
Nothing else covers it
- The orphan sweep in
session_pid.pygates on MCP launcher shapes (@playwright/mcp,
mcp start-server); acliDaemon.jscmdline matches neither. kill_process_treeon session teardown cannot reach it — the daemon is reparented to
pid 1 and is not in the ACP process tree.- The agent cannot clean up after itself unattended:
close/close-all/kill-allare
deliberately fail-closed in the browser-CLI approval gate. - There is no TTL or idle timeout. A 4-day-old daemon with no live session is not waiting
on anything.
Today the only routine reclamation is an operator running playwright-cli close-all by
hand, which also takes down live sessions.
Directions worth considering
- Reclaim by process tree, not by CLI verb. A
cliDaemonreparented to pid 1 whose
session name (available asargv[1]) has no live registry entry is structurally
identifiable without any state we have to store — and therefore without anything an
agent could forge. Killing the tree does not need the lost socket. - Stop putting the socket somewhere KiroCrew reclaims. If the CLI's socket dir lived
outside per-process scratch, a daemon would stay reachable across its owner's death and
an ordinaryclosewould work. This may be the more correct fix; it needs a decision
about whetherTMPDIRcontainment should carve out this case. - Reap before reclaiming the scratch dir.
sweep_dead_scratchknows the owner is
dead and still has the socket at that moment — the one point in time where a graceful
closewould succeed.
Direction 1 is available today; 2 and 3 need a design call about scratch containment.
Notes
- Human-triggered rather than timer-driven reclamation avoids a separate problem found in
review: a gateway that releases browsers unattended, based on on-disk state, converts an
agent-writable file into an unattendedclose— laundering a verb the approval gate
fails closed on. A verb an operator runs does not have that property. - Per-session browser naming (Agent sessions share one playwright-cli browser, so concurrent sessions steal and close each other's pages #5952) neither causes nor cures this: the strand happens per
session process regardless of naming. It changes only how many browsers are live at once.
Activity
github-actions commentedon Aug 26, 2026
Automated triage summary — generated from the issue text alone, before any maintainer has read it. Treat every line as a starting point, not a verdict.
Playwright-cli browser daemons spawned by agent sessions outlive their agent, reparent to pid 1, and are never reclaimed because their control socket lived in the per-process scratch dir that gets swept, leaving them unreachable and untracked by the CLI registry. On one long-running gateway host the reporter measured 19 such daemons (oldest 4d8h) holding ~10.7 GB with no live session claiming them.
Possibly already reported
If one of these is the same problem, say so and we will close this as a duplicate.
Posted by
issue-summary.yml. The model had no access to the repository — it read only this issue and the titles of recent ones.CrysisDeu commentedon Aug 26, 2026
🤖 Kiro Crew Auto-Pipeline [operator: CrysisDeu#0c98c3a2]: Routing to needs-investigation. The leak mechanism is fully diagnosed (control socket lives under per-process scratch TMPDIR; sweep_dead_scratch reclaims the dir, stranding an unreachable pid-1 daemon that no CLI verb can release), but the fix requires a design decision between the two directions the report itself lays out: (1) a reap-by-process-tree sweep keyed on cliDaemon argv[1] vs live registry entries — mechanically derivable but it is a process-killing reaper with forgeability/safety review needs, and an equivalent was deliberately removed from PR #5952; or (2) relocating the CLI socket dir outside per-process scratch, which is likely more correct but changes TMPDIR-containment semantics and needs an owner's call. A human should decide the direction (or both: relocation for reachability + a conservative tree-reaper for existing strays); after that decision the implementation is dispatchable.
Tiger-0512 commentedon Aug 26, 2026
Implemented direction 1 (reclaim by process identity, no CLI verb, no registry trust) in #6083.
Verified the mechanism on a live host before writing code: a pid-1-reparented
cliDaemon.jshere carries the exec-timeKIROCREW_SPAWNED=1marker and aTMPDIRpointing at a since-swept managed scratch dir — evidence an agent cannot forge for another process, and enough to reclaim without touching the CLI or its registry. The kill pins the process start token across validation and signals only a self-led process group, so a recycled PID or an attached operator browser is never a target; Windows fails closed.Directions 2 and 3 (socket relocation out of per-process scratch / reap at scratch-sweep time) stay open here as the design-call half — this closes the standing leak without changing TMPDIR containment semantics.
fix(browser): reap detached playwright-cli daemons with no live owner
fix(browser): reap detached playwright-cli daemons with no live owner
fix(browser): reap detached playwright-cli daemons with no live owner
fix(browser): keep generated sessions reachable after scratch cleanup
Tiger-0512 commentedon Aug 27, 2026
Update on #6083 after the review rounds: the automatic process-table reaper from my earlier comment has been fully withdrawn.
The reviews correctly established that cross-process SIGKILL cannot be authorized safely from scratch metadata (agent-writable), a process-local live set (misses concurrent same-home managers), or a token-only numeric PID/group check on macOS (no pidfd-equivalent identity pin). The PR now contains no automatic close or process signal.
The replacement scope is the smaller safe part of direction 2: for Kiro-Crew-generated per-process browser sessions only, keep
PWTEST_SOCKETS_DIRandPWTEST_DAEMON_SESSION_DIRoutside agent scratch. The daemon remains reachable through ordinaryplaywright-cli list/-s=<name> closeafter scratch is reclaimed. Operator session locations are untouched. Crash-orphan reclamation remains open in this issue.fix(browser): keep generated sessions reachable after scratch cleanup
fix(browser): keep generated sessions reachable after scratch cleanup
fix(browser): keep generated sessions reachable after scratch cleanup
fix(browser): keep generated sessions reachable after scratch cleanup (…
fix(browser): reclaim stranded playwright-cli browser daemons
fix(browser): reclaim stranded playwright-cli browser daemons (#7294)