Skip to content

fix(webapp-testing): kill the server's process group so servers aren't orphaned - #1593

Open
romansrepairsltd-coder wants to merge 1 commit into
anthropics:mainfrom
romansrepairsltd-coder:fix/with-server-kill-process-group
Open

romansrepairsltd-coder wants to merge 1 commit into
anthropics:mainfrom
romansrepairsltd-coder:fix/with-server-kill-process-group

Conversation

@romansrepairsltd-coder

Copy link
Copy Markdown

Problem

scripts/with_server.py launches each server with shell=True and stops it with process.terminate(). That signals the shell, not the server the shell spawned. For any command of the documented multi-server form:

python scripts/with_server.py \
  --server "cd backend && python server.py" --port 3000 \
  --server "cd frontend && npm run dev" --port 5173 \
  -- python test.py

the shell dies and the real server is orphaned, still bound to its port. The script prints All servers stopped while the process is very much alive, and the next run fails with EADDRINUSE / Address already in use.

Reproduction

python scripts/with_server.py --server "cd site && python3 -m http.server 8917" --port 8917 -- true
ss -ltn | grep 8917   # still listening after "All servers stopped"

Fix

  • start_new_session=True puts the shell and everything it spawns into one process group.
  • Cleanup signals that group with os.killpg — SIGTERM first, SIGKILL after the existing 5s timeout.
  • ProcessLookupError is tolerated on both paths, for a server that already exited on its own.

Behaviour is unchanged when the server is a direct child; this only extends the reach of the existing terminate/kill escalation.

Verification

Reproduced the orphan on Linux (Ubuntu 24.04, Python 3.12), confirmed the port stays bound after cleanup, applied the patch, and confirmed the port is released. Checked with a page whose content is set by JS, so the run also exercises the documented networkidle path via Playwright before teardown.

🤖 Generated with Claude Code

…t orphaned

with_server.py starts servers with shell=True and stops them with
process.terminate(), which signals only the shell. A server started as
`cd frontend && npm run dev` outlives cleanup and keeps holding its port,
so the next run fails with EADDRINUSE.

Put the shell and its children in one process group via start_new_session,
then signal that group (SIGTERM, SIGKILL after the 5s timeout).

Co-Authored-By: Claude Opus 5 <[email protected]>
@98zc5g5jyw-arch

Copy link
Copy Markdown

Babysit review — LGTM ✅

start_new_session + os.killpg (with SIGKILL fallback and win32 branch) properly kills the server's process group — real fix for orphaned servers, more complete than the overlap in #1602. Good.

@98zc5g5jyw-arch 98zc5g5jyw-arch left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hermes Review

Verdict: Approve — correct and well-handled fix for orphaned server processes.

Verified

  • start_new_session=True puts the shell + children in a fresh process group; os.killpg(pgid, SIGTERM) then reaches the real server, not just the shell — fixes the orphaned-port problem described.
  • ProcessLookupError is caught both at getpgid time and around killpg/terminate() — the fallback path is safe for already-dead processes.
  • SIGTERM → 5s wait → SIGKILL escalation is a sane cleanup ladder.

Suggestions (non-blocking)

  • Consider also os.setpgid-independent portability note: start_new_session is POSIX-only; on Windows this file's behavior changes (killpg unavailable) — a short comment or guard would help Windows contributors.
  • process.terminate() in the pgid is None branch can still raise ProcessLookupError, but it's inside the try → covered by the except (TimeoutExpired, ProcessLookupError) handler. Good.

Reviewed by Hermes Agent (cron babysitter)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants