fix(browse): identity-based terminal-agent kill replaces pkill regex

Commit 0 of the v1.44 long-lived-sidebar PR — foundation for the watchdog
and removes a latent cross-session footgun.

`pkill -f terminal-agent\.ts` (cli.ts spawn site + server.ts shutdown) matched
by argv regex and would kill ANY process whose argv contained the string —
sibling gstack sessions on the same host, an editor with the file open, a
second `$B connect` run. Identity-based PID kill via a new helper module
removes that whole class of bug.

  * New `browse/src/terminal-agent-control.ts`: `readAgentRecord`,
    `writeAgentRecord`, `clearAgentRecord`, `killAgentByRecord`. Validates
    PID liveness via `isProcessAlive` before signaling (PID-reuse defense).
  * `terminal-agent.ts` writes `<stateDir>/terminal-agent-pid` (JSON
    `{pid, gen, startedAt}`) at boot; clears on SIGTERM/SIGINT.
  * New per-boot `CURRENT_GEN` (16-byte random); `/internal/*` callers can
    include `X-Browse-Gen` to defend against split-brain in the upcoming
    watchdog. Absent header is accepted (backward compat); mismatch returns
    409. New `checkInternalAuth` helper centralizes bearer + gen checks.
  * New `/internal/healthz` route — agent liveness probe used by the
    upcoming watchdog (returns pid/gen/sessions, no claude-binary lookup).
  * `cli.ts` and `server.ts` both call `killAgentByRecord` instead of pkill.
  * `ServerConfig.ownsTerminalAgent` JSDoc updated; the gated teardown now
    runs 4 side effects (was 3) — adds the new agent-record unlink.

Test changes:

  * New `browse/test/terminal-agent-pid-identity.test.ts` — static-grep
    tripwire that fails CI if any source file re-introduces `pkill ...
    terminal-agent` or `spawnSync('pkill', ...)`; round-trips
    write/read/clear; verifies killAgentByRecord no-ops on dead PIDs.
  * `browse/test/server-embedder-terminal-port.test.ts` rewritten to
    intercept `process.kill` (not `child_process.spawnSync`); writes a
    sentinel agent-record with a guaranteed-dead PID; asserts probe-only
    (signal 0) calls, no termination signals; verifies all 3 discovery
    files including the new terminal-agent-pid.

Closes TODOS.md P3 ("Identity-based terminal-agent kill").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-23 18:29:33 -07:00
parent 1d9b9c4cfc
commit 3af07a0c23
8 changed files with 447 additions and 108 deletions
+10 -27
View File
@@ -2,34 +2,17 @@
## browse server: terminal-agent teardown follow-ups (filed v1.41 via /plan-eng-review)
### P3: Identity-based terminal-agent kill (replace pkill regex with PID)
### ✅ DONE (v1.44.0.0): Identity-based terminal-agent kill (replace pkill regex with PID)
**What:** Record the spawned terminal-agent PID at `browse/src/cli.ts:1057` and
replace `pkill -f terminal-agent\.ts` at both `cli.ts:1047` and
`server.ts:1281` (now inside the `if (ownsTerminalAgent)` gate) with
`process.kill(pid, signal)` against the recorded PID.
**Why:** `pkill -f terminal-agent\.ts` matches by command-line regex, so today
it can kill ANY process whose argv contains `terminal-agent.ts` — sibling
gstack sessions, editor processes that have the file open, a second gstack
run on the same host. Latent footgun for the CLI path, not just embedders.
**Pros:** Removes a real cross-session foot-cannon. PID-based kill is the
correct identity primitive. Lets us tighten `pkill -f`'s broad-match warning
in the new `ownsTerminalAgent` JSDoc to "historical" rather than "current".
**Cons:** Requires threading the PID through the CLI-to-server state path
(currently the parent server reads `terminal-port` to discover the agent; it
would also need `terminal-agent-pid`). Touches `cli.ts`, `server.ts`, and
`terminal-agent.ts` together — bigger surface than the v1.41 fix.
**Context:** Surfaced by both Codex and Claude subagent during /autoplan
review of the `ownsTerminalAgent` gate. Currently documented as out-of-scope
in `browse/src/server.ts` JSDoc for `ServerConfig.ownsTerminalAgent`. The
embedder fix (ownsTerminalAgent: false) means embedders don't hit this; CLI
users still do.
**Depends on:** None.
**Resolved:** Bundled into the v1.44.0.0 long-lived-sidebar PR as Commit 0.
`browse/src/terminal-agent-control.ts` is the new home for `readAgentRecord`,
`writeAgentRecord`, `clearAgentRecord`, and `killAgentByRecord`. The agent
writes `<stateDir>/terminal-agent-pid` (JSON `{pid, gen, startedAt}`) at boot
and clears it on SIGTERM/SIGINT. `cli.ts` and `server.ts` both route through
`killAgentByRecord` instead of `pkill -f terminal-agent\.ts`. The new
`browse/test/terminal-agent-pid-identity.test.ts` is the static-grep tripwire
that fails CI if `pkill ... terminal-agent` or `spawnSync('pkill', ...)`
reappears in any source file.
---