Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix(browse): stop isProcessAlive from flashing a Windows console every watchdog tick#1979

Open
jbetala7 wants to merge 1 commit into
garrytan:main from
jbetala7:oss/fix-1952-isprocessalive-windowshide
Open

fix(browse): stop isProcessAlive from flashing a Windows console every watchdog tick #1979
jbetala7 wants to merge 1 commit into
garrytan:main from
jbetala7:oss/fix-1952-isprocessalive-windowshide

Conversation

@jbetala7

@jbetala7 jbetala7 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Problem

On Windows, a connected headed browse session blinks a conhost.exe console window on a fixed interval for the entire session — no CLI command, no user action. The terminal-agent watchdog calls isProcessAlive() once per setInterval tick (default 60s) to check the agent PID, and on Windows isProcessAlive() shelled out to tasklist, which Windows gives its own console window. macOS/Linux stayed silent because their branch is a process.kill(pid, 0) syscall that spawns nothing.

It is purely cosmetic — each window self-closes — but it blinks on a fixed interval for the whole session with no indication of what it is.

Root cause

isProcessAlive() (browse/src/error-handling.ts) had a Windows-only branch that spawned tasklist:

if (IS_WINDOWS) {
 const result = Bun.spawnSync(['tasklist', '/FI', `PID eq ${pid}`, '/NH', '/FO', 'CSV'], {...});
 return result.stdout.toString().includes(`"${pid}"`);
}

Every watchdog tick → a tasklist spawn → a console window. The parent watchdog two functions away already avoids exactly this by calling process.kill(pid, 0) directly; only isProcessAlive() took the spawn path.

Fix

Unify on signal-0 across all platforms. Node/Bun implement process.kill(pid, 0) on Windows via OpenProcess — a pure existence check, no child process, no window. The Windows-only branch (and its now-unused IS_WINDOWS const) are removed.

This also corrects a latent edge: the old branch collapsed every error to false, so a live-but-unsignalable process (EPERM) read as dead. Now ESRCH → dead, EPERM → alive.

export function isProcessAlive(pid: number): boolean {
 try {
 process.kill(pid, 0);
 return true;
 } catch (err: any) {
 // ESRCH → gone (false); EPERM → exists but not signallable → still alive.
 return err?.code === 'EPERM';
 }
}

The same isProcessAlive is compiled into the browse CLI binary's polling loops, so the one-file fix covers those on rebuild too. The watchdog's alive/dead branch is unchanged — signal-0 returns the same boolean it already keys on, so dead-agent detection and respawn still work.

Testing

bun test browse/test/error-handling.test.ts9/9 pass. Added:

  • an EPERM-as-alive regression case (probes PID 1, which a non-root user can't signal), and
  • a static tripwire asserting the isProcessAlive source never reintroduces tasklist/spawn — the idiomatic gstack guard for a Windows-only cosmetic bug that can't be exercised on macOS CI. Proven to fail on the old tasklist body before the fix.

The watchdog/terminal-agent suites (terminal-agent-watchdog.test.ts, watchdog.test.ts, etc.) stay green. One unrelated pre-existing failure in terminal-agent.test.ts (a source-guard grepping for spawnClaude(, since renamed to maybeSpawnPty) reproduces on clean main with this branch stashed — not touched here (this PR only changes browse/src/error-handling.ts + its test).

Fixes #1952

...y watchdog tick
On Windows, isProcessAlive() shelled out to `tasklist` via Bun.spawnSync.
Windows gives that child its own console window, so the terminal-agent
watchdog's per-tick existence check (default 60s) flashed a conhost.exe
window for the entire headed session — purely cosmetic but a constant,
unexplained blink (garrytan#1952). macOS/Linux stayed silent because their branch
is a `process.kill(pid, 0)` syscall that spawns nothing.
Unify on signal-0 across all platforms — exactly what the parent watchdog
two functions away already does. Node/Bun implement `process.kill(pid, 0)`
on Windows via OpenProcess: a pure existence check, no child process, no
window. This also fixes a latent correctness edge: the old branch collapsed
every error to false, so a live-but-unsignalable process (EPERM) read as
dead. Now ESRCH -> dead, EPERM -> alive.
Tests: existing alive/dead probes still pass; add an EPERM-as-alive case and
a static tripwire asserting the probe never reintroduces `tasklist`/spawn
(proven to fail on the old body). 9/9 green in error-handling.test.ts.
Fixes garrytan#1952
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

trunk-io Bot commented Jun 11, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

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

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

terminal-agent watchdog flashes a console window every tick on Windows — isProcessAlive() spawns tasklist instead of signal-0

1 participant

AltStyle によって変換されたページ (->オリジナル) /