test:free leaves orphaned browse/src/server.ts processes after shards finish
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
`bun run test:free` leaves `browse/src/server.ts` processes running with
`ppid=1` after every run. They are not reaped when the shard that spawned them
ends, and they survive indefinitely.
**Observed** — macOS 26.5.2 (arm64), bun 1.3.13, gstack `v1.80.0.0`.
Two `bun run test:free` runs, started 20:44:17 and 20:52:43. Process table at
21:06, roughly thirteen minutes after the first run finished:
```
PID PPID STARTED COMMAND
98417 1 20:44:20 bun run .../gstack/browse/src/server.ts
2065 1 20:44:33 bun run .../gstack/browse/src/server.ts
5820 1 20:44:46 bun run .../gstack/browse/src/server.ts
20882 1 20:45:18 bun run .../gstack/browse/src/server.ts
25602 1 20:52:58 bun run .../gstack/browse/src/server.ts
29406 1 20:53:12 bun run .../gstack/browse/src/server.ts
44323 1 20:53:42 bun run .../gstack/browse/src/server.ts
```
Seven servers — four from the first run, three from the second — each started
within the first ~60s of its run. Three `browse/src/terminal-agent.ts` children
were still attached to them.
The first run also hit `shard 2/6 exceeded the 435s wall-clock deadline —
killed the process group`, so at least some of these outlived an explicit
group kill. The second run completed normally — every shard reported — and it
still left three.
**Why the existing cleanup does not catch them.** `runShardChild` calls
`killProcessGroup(child, 'SIGKILL')` on both the timeout path and the clean
path ("Reap survivors of this shard even on the clean path",
`scripts/test-free-shards.ts:1293`). That kills the *shard's* process group.
A child spawned with `detached: true` becomes the leader of its own group, so
a group kill aimed at the shard does not reach it — which is the same property
`session-runner.ts` relies on deliberately when it calls `killProcessGroup` on
its own child. Anything that starts a browse server and does not itself group-kill
it on teardown therefore leaks, and a shard killed from outside never runs its
in-process cleanup at all.
**Impact.** Each leaked server holds a port and memory for the lifetime of the
machine session. Seven accumulated from two runs; a day of local test iterations
would leave dozens. It also makes later runs noisier — a stale server on a
port a fresh test expects is a plausible source of the kind of intermittent
failure that gets triaged as flake.
**Not reported here.** I could not confirm the related claim that the
`claude` provider itself is orphaned; no orphaned `claude` process appeared in
the same window, and `session-runner.ts:266-271` group-kills it explicitly.
**Suggested direction** (not a patch — the right owner boundary is not obvious
from outside): have whatever starts a browse server for a test register it for
group-kill on shard teardown, the way `session-runner.ts` does for its own
child, so the reap on `scripts/test-free-shards.ts:1293` covers it.
**Cleanup is harder than it looks** (found while reaping these by hand):
- `browse stop` cannot reach them. It resolves the daemon through `readState()`
(`/.gstack/browse.json`), and shards run with an isolated state dir that
`runShardChild` deletes on teardown (`fs.rmSync(stateDir, …)`). The state file
is gone while the process lives on, so the supported stop path has nothing to
look up.
- They ignore `SIGTERM`. Seven servers survived a group `SIGTERM`; only group
`SIGKILL` cleared them.
- Killing the `terminal-agent` alone is not enough — the server respawns it
(`terminal-agent-control.ts` names a "v1.44 watchdog/respawn loop"). A new
agent appeared ~17 minutes after the run, re-parented to a leaked server.
Kill the server, not the agent.
**Repro**
```bash
bun run test:free
# after it finishes:
ps -eo pid,ppid,lstart,command | grep '[b]rowse/src/server.ts' | awk '$2==1'
```
Contributor guide
Research direction
Start with scripts/test-free-shards.ts:1293 and trace how browse/src/server.ts is started for each shard, comparing its lifecycle with session-runner.ts. Reproduce with bun run test:free and inspect for orphaned server.ts processes after clean completion and timeout; done means the shard teardown reaps those servers without leaving them behind.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bun, typescript
- Domain
- testing-qa, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100