Skip to content

fix: Windows process cleanup for Cue and tunnel on shutdown#788

Merged
pedramamini merged 3 commits intoRunMaestro:rcfrom
chr1syy:fix/windows-closing-hang
Apr 12, 2026
Merged

fix: Windows process cleanup for Cue and tunnel on shutdown#788
pedramamini merged 3 commits intoRunMaestro:rcfrom
chr1syy:fix/windows-closing-hang

Conversation

@chr1syy
Copy link
Copy Markdown
Contributor

@chr1syy chr1syy commented Apr 10, 2026

Summary

  • Cue processes survive shutdown: performCleanup() calls processManager.killAll() but Cue processes are tracked separately in cue-executor.ts's activeProcesses map — they were never killed on quit. Added stopAllCueRuns() and call it during shutdown.
  • Cue executor uses SIGTERM/SIGKILL on Windows: stopCueRun() and timeout enforcement sent POSIX signals which don't terminate process trees on Windows. Added killCueProcess() helper that uses taskkill /t /f on Windows.
  • Tunnel manager uses SIGTERM/SIGKILL on Windows: TunnelManager.stop() sent POSIX signals. Now uses taskkill /t /f on Windows for the cloudflared process tree.

Changes

File Change
src/main/cue/cue-executor.ts Added killCueProcess() (Windows: taskkill, POSIX: SIGTERM→SIGKILL), stopAllCueRuns() export, refactored stopCueRun() and timeout to use it
src/main/tunnel-manager.ts stop() now uses taskkill /t /f on Windows instead of SIGTERM/SIGKILL
src/main/app-lifecycle/quit-handler.ts performCleanup() now calls stopAllCueRuns() before killAll()

Test plan

  • TypeScript type check passes
  • All 48 cue-executor tests pass
  • All 21 tunnel-manager tests pass
  • All 21 quit-handler tests pass
  • Prettier formatting clean
  • Manual: verify on Windows that closing Maestro with active Cue runs leaves no orphaned processes in Task Manager

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Shutdown now reliably stops all active background runs before killing remaining processes and uses platform-aware termination to avoid lingering processes on Windows and POSIX.
    • Bulk termination now ensures Windows process trees are synchronously terminated during shutdown.
  • Tests

    • Updated lifecycle and process tests to verify background runs are stopped prior to general process-kill steps and to validate termination ordering and behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fdc2898c-0a6d-4012-b2b4-222b7f6ce934

📥 Commits

Reviewing files that changed from the base of the PR and between bc5ebae and f0813f0.

📒 Files selected for processing (4)
  • src/__tests__/main/process-manager.test.ts
  • src/main/cue/cue-process-lifecycle.ts
  • src/main/process-manager/ProcessManager.ts
  • src/main/tunnel-manager.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/cue/cue-process-lifecycle.ts

📝 Walkthrough

Walkthrough

Adds shutdown termination for active Cue runs and platform-aware process-tree termination for Cue/tunnel processes; integrates stopAllCueRuns into quit flow and updates tests and ProcessManager to support synchronous Windows taskkill behavior.

Changes

Cohort / File(s) Summary
Cue process lifecycle & executor
src/main/cue/cue-process-lifecycle.ts, src/main/cue/cue-executor.ts
Introduce killCueProcess(child, sync?) with Windows taskkill (sync option) and POSIX SIGTERM→conditional SIGKILL; add stopAllProcesses() and export stopAllCueRuns() that delegates to it. Replace per-run timeout kill logic with the helper.
Quit handler integration & tests
src/main/app-lifecycle/quit-handler.ts, src/__tests__/main/app-lifecycle/quit-handler.test.ts
Call stopAllCueRuns() (logged) during performCleanup() before processManager?.killAll(); tests mock/spy stopAllCueRuns and assert ordering relative to killAll.
Process manager & Windows sync kill
src/main/process-manager/ProcessManager.ts, src/__tests__/main/process-manager.test.ts
Add { sync?: boolean } to kill(...); implement killWindowsProcessTree(..., sync?) using execFileSync when sync true; have killAll() call kill(..., { sync: true }). Test expectations updated to assert sync propagation.
Tunnel termination (platform-aware)
src/main/tunnel-manager.ts
TunnelManager.stop() now uses Windows taskkill /pid <pid> /t /f via execFileSync when pid exists; non-Windows still use SIGTERM with conditional SIGKILL on timeout.
Tests / mocks
src/__tests__/main/cue/cue-executor.test.ts
Extend MockChildProcess with exitCode and signalCode properties to observe termination type; tests adapted where needed.

Sequence Diagram

sequenceDiagram
    participant App as App Lifecycle
    participant Quit as Quit Handler
    participant CueExec as Cue Executor
    participant Tunnel as Tunnel Manager
    participant ProcMgr as Process Manager

    App->>Quit: performCleanup()
    Quit->>Quit: grooming-session cleanup (optional)
    Quit->>CueExec: stopAllCueRuns()
    rect rgba(100,150,255,0.5)
    CueExec->>CueExec: iterate activeProcesses
    CueExec->>CueExec: killCueProcess(child, sync?)  -- Windows: taskkill /pid /t /f (sync on shutdown) / POSIX: SIGTERM → SIGKILL if needed
    CueExec->>CueExec: remove entries from activeProcesses
    end
    Quit->>Tunnel: stop()
    rect rgba(100,200,150,0.5)
    Tunnel->>Tunnel: If Windows: execFileSync("taskkill /pid <pid> /t /f")
    Tunnel->>Tunnel: Else: send SIGTERM, wait, then SIGKILL on timeout
    Tunnel->>Quit: stop() complete
    end
    Quit->>ProcMgr: killAll() (sync on Windows)
    ProcMgr->>ProcMgr: kill(..., { sync: true }) -> ensure taskkill completes
    Quit->>App: Cleanup complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

approved

Poem

🐰 Hopping through shutdown's gentle hum,
I call the runs and say, "All done."
Taskkill nibbles Windows trees,
POSIX bows with gentle pleas,
Quiet now — carrot snack, then some.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the primary fix: Windows process cleanup for Cue and tunnel processes during application shutdown.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 10, 2026

Greptile Summary

This PR fixes two Windows-specific process cleanup bugs: (1) Cue-spawned processes were tracked separately from ProcessManager and never killed on shutdown, and (2) both cue-executor.ts and tunnel-manager.ts used POSIX signals (SIGTERM/SIGKILL) which don't terminate process trees on Windows. The fix introduces killCueProcess() (uses taskkill /t /f on Windows, SIGTERM→SIGKILL on POSIX), exports stopAllCueRuns(), and wires it into performCleanup() before killAll(). The tunnel manager gets the same Windows-aware kill path. The logic is sound and the stated test counts are encouraging.

Confidence Score: 5/5

Safe to merge — fixes are targeted, correct, and well-tested; no blocking issues found.

All three files make narrow, well-scoped changes. The Windows taskkill /t /f path is the established pattern for child-process-tree termination on Windows. POSIX behaviour is unchanged. stopAllCueRuns() is correctly wired into the shutdown sequence before killAll(), and the Map-iteration deletion pattern is safe in JavaScript. All findings are P2 or lower.

No files require special attention.

Important Files Changed

Filename Overview
src/main/cue/cue-executor.ts Added killCueProcess() (Windows: taskkill /t /f; POSIX: SIGTERM then SIGKILL after 5 s), stopAllCueRuns() export, and refactored stopCueRun() and timeout enforcement to use the shared helper.
src/main/tunnel-manager.ts Added Windows-aware kill path in stop(): uses taskkill /t /f on Windows, SIGTERM+SIGKILL-after-3s on POSIX. The proc.once('exit', ...) listener correctly clears the fallback timeout on both paths.
src/main/app-lifecycle/quit-handler.ts Imports and calls stopAllCueRuns() in performCleanup() before killAll(), correctly ordering Cue process cleanup ahead of the main ProcessManager sweep.

Sequence Diagram

sequenceDiagram
    participant User
    participant App as Electron App
    participant QH as QuitHandler
    participant CE as CueExecutor
    participant TM as TunnelManager
    participant OS as OS / taskkill

    User->>App: Cmd+Q / Quit
    App->>QH: before-quit event
    QH->>QH: performCleanup()
    QH->>CE: stopAllCueRuns()
    loop Each active Cue process
        CE->>OS: taskkill /pid /t /f (Windows) or SIGTERM->SIGKILL (POSIX)
        CE->>CE: activeProcesses.delete(runId)
    end
    QH->>App: processManager.killAll()
    QH->>TM: tunnelManager.stop()
    TM->>OS: taskkill /pid /t /f (Windows) or SIGTERM->SIGKILL (POSIX)
    TM-->>QH: await exit / timeout (3 s)
    QH->>App: deleteCliServerInfo(), closeStatsDB()
Loading

Reviews (1): Last reviewed commit: "fix: use taskkill on Windows for Cue and..." | Re-trigger Greptile

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/main/cue/cue-executor.ts (1)

442-457: Well-structured platform-aware termination helper.

The killCueProcess helper correctly:

  1. Uses taskkill /t /f on Windows to terminate the entire process tree
  2. Uses SIGTERM with delayed SIGKILL escalation on POSIX
  3. Checks exitCode === null && signalCode === null before SIGKILL to avoid signaling an already-exited process

Same feedback as tunnel-manager.ts: consider adding debug logging for unexpected taskkill failures to aid debugging orphaned processes on Windows.

,

🔧 Optional: Add debug logging for taskkill errors
 function killCueProcess(child: ChildProcess): void {
 	if (isWindows() && child.pid) {
-		execFile('taskkill', ['/pid', String(child.pid), '/t', '/f'], () => {
-			// taskkill returns non-zero if the process is already dead, which is fine
+		execFile('taskkill', ['/pid', String(child.pid), '/t', '/f'], (error) => {
+			// taskkill returns non-zero if the process is already dead, which is fine.
+			// Log other errors for debugging.
+			if (error && !error.message.includes('not found')) {
+				// Import logger if not already available, or use console.debug for minimal overhead
+			}
 		});
 	} else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/cue/cue-executor.ts` around lines 442 - 457, In killCueProcess, the
Windows branch calls execFile('taskkill', ...) but currently ignores errors;
modify that callback to log unexpected taskkill failures for debugging orphaned
processes by capturing the (err, stdout, stderr) arguments and emitting a
debug-level log that includes the error and stderr output (but do not change
control flow or treat non-zero exit as fatal). Update the callback inside
killCueProcess where execFile is invoked so it logs the details (err and stderr)
using the module's logger (e.g., processLogger or the existing logger used
elsewhere in this file) while keeping the existing comment about non-zero return
codes.
src/main/tunnel-manager.ts (1)

109-117: Consider logging taskkill failures for debugging orphaned process issues.

The empty callback swallows all execFile errors. While ignoring "process already dead" (non-zero exit) is reasonable, unexpected failures (e.g., taskkill not found, permission denied) would be silently lost, making it harder to debug orphaned process issues on Windows.

🔧 Proposed fix to add error logging
 		if (isWindows() && proc.pid) {
 			// On Windows, POSIX signals don't terminate process trees.
 			// Use taskkill /t /f to kill the cloudflared process and its children.
-			execFile('taskkill', ['/pid', String(proc.pid), '/t', '/f'], () => {
-				// taskkill returns non-zero if the process is already dead, which is fine
+			execFile('taskkill', ['/pid', String(proc.pid), '/t', '/f'], (error) => {
+				// taskkill returns non-zero if the process is already dead, which is fine.
+				// Log other errors for debugging orphaned process issues.
+				if (error && !error.message.includes('not found')) {
+					logger.debug(`taskkill for cloudflared: ${error.message}`, 'TunnelManager');
+				}
 			});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/tunnel-manager.ts` around lines 109 - 117, The taskkill callback
currently swallows all errors; update the execFile('taskkill', ['/pid',
String(proc.pid), '/t', '/f'], ...) callback to check for an error and log
unexpected failures (e.g., taskkill not found or permission denied) using the
module's existing logger instead of ignoring them, while still treating non-zero
exit when the process is already dead as benign; keep the else branch that calls
proc.kill('SIGTERM') unchanged and reference isWindows(), proc.pid and
execFile('taskkill'...) when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/cue/cue-executor.ts`:
- Around line 442-457: In killCueProcess, the Windows branch calls
execFile('taskkill', ...) but currently ignores errors; modify that callback to
log unexpected taskkill failures for debugging orphaned processes by capturing
the (err, stdout, stderr) arguments and emitting a debug-level log that includes
the error and stderr output (but do not change control flow or treat non-zero
exit as fatal). Update the callback inside killCueProcess where execFile is
invoked so it logs the details (err and stderr) using the module's logger (e.g.,
processLogger or the existing logger used elsewhere in this file) while keeping
the existing comment about non-zero return codes.

In `@src/main/tunnel-manager.ts`:
- Around line 109-117: The taskkill callback currently swallows all errors;
update the execFile('taskkill', ['/pid', String(proc.pid), '/t', '/f'], ...)
callback to check for an error and log unexpected failures (e.g., taskkill not
found or permission denied) using the module's existing logger instead of
ignoring them, while still treating non-zero exit when the process is already
dead as benign; keep the else branch that calls proc.kill('SIGTERM') unchanged
and reference isWindows(), proc.pid and execFile('taskkill'...) when making the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c72d4907-a457-4138-8e00-1318ea8feaee

📥 Commits

Reviewing files that changed from the base of the PR and between 578e3df and f489ede.

📒 Files selected for processing (3)
  • src/main/app-lifecycle/quit-handler.ts
  • src/main/cue/cue-executor.ts
  • src/main/tunnel-manager.ts

Cue process lifecycle and TunnelManager used POSIX signals (SIGTERM/SIGKILL)
to terminate child processes, which don't work for shell-spawned process
trees on Windows — leaving orphaned processes after Maestro closes.

Changes:
- cue-process-lifecycle: add killCueProcess() helper using taskkill /t /f on
  Windows, falling back to SIGTERM→SIGKILL on POSIX
- cue-process-lifecycle: add stopAllProcesses() to kill all active Cue processes
- cue-executor: export stopAllCueRuns() delegating to lifecycle module
- tunnel-manager: use taskkill /t /f on Windows for cloudflared cleanup
- quit-handler: call stopAllCueRuns() during shutdown (Cue processes are
  tracked separately from ProcessManager and were never killed on quit)
- quit-handler test: mock cue-executor, verify stopAllCueRuns call order

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chr1syy chr1syy force-pushed the fix/windows-closing-hang branch from b971b95 to 8740ed5 Compare April 11, 2026 08:00
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/cue/cue-process-lifecycle.ts`:
- Around line 101-105: The Windows branch of killCueProcess currently swallows
errors from execFile('taskkill', ...), so update the execFile callback in
killCueProcess to explicitly handle errors: if err indicates taskkill failed for
an unexpected reason, call Sentry utilities (captureException or captureMessage)
and do not clear the process tracking entry; if the error is the expected
"process not found"/non-zero exit for already-dead process, treat it as success
and proceed to clear tracking; on success clear tracking as before. Use the
existing execFile call signature and refer to captureException/captureMessage
for reporting and the same process-tracking removal logic currently executed at
the removal point so that removal only happens after confirming success or an
acceptable “already dead” condition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 11fd6047-5fcc-4478-af4b-abfc16c5b6ea

📥 Commits

Reviewing files that changed from the base of the PR and between b971b95 and 8740ed5.

📒 Files selected for processing (5)
  • src/__tests__/main/app-lifecycle/quit-handler.test.ts
  • src/main/app-lifecycle/quit-handler.ts
  • src/main/cue/cue-executor.ts
  • src/main/cue/cue-process-lifecycle.ts
  • src/main/tunnel-manager.ts
✅ Files skipped from review due to trivial changes (1)
  • src/main/app-lifecycle/quit-handler.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/tunnel-manager.ts

Comment thread src/main/cue/cue-process-lifecycle.ts Outdated
- Add explicit error handling to taskkill callback in killCueProcess:
  ignore expected "not found" / "no running instance" errors, report
  unexpected failures to Sentry via captureException.
- Add exitCode and signalCode properties to MockChildProcess in
  cue-executor tests so the SIGKILL escalation guard condition works
  correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/main/cue/cue-process-lifecycle.ts (1)

267-271: ⚠️ Potential issue | 🟠 Major

Do not untrack processes before kill outcome is known.

stopAllProcesses() deletes entries immediately after killCueProcess(). On Windows, taskkill is async; if it fails, the process can survive while being removed from tracking.

Suggested fix
 export function stopAllProcesses(): void {
 	for (const [runId, entry] of activeProcesses) {
+		const clear = () => activeProcesses.delete(runId);
+		entry.child.once('close', clear);
+		entry.child.once('error', clear);
 		killCueProcess(entry.child);
-		activeProcesses.delete(runId);
 	}
 }

As per coding guidelines: "Do not silently swallow errors... Handle expected/recoverable errors explicitly... For unexpected errors, re-throw them to allow Sentry to capture them."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/cue/cue-process-lifecycle.ts` around lines 267 - 271,
stopAllProcesses currently removes entries from activeProcesses before the kill
outcome is known, which can leave orphaned processes on Windows; change
stopAllProcesses to await the kill outcome and only call
activeProcesses.delete(runId) after killCueProcess(entry.child) completes
successfully, wrapping the await in a try/catch so failures are either handled
(log/record) or re-thrown per guidelines; if killCueProcess is synchronous,
convert it to return a Promise (or provide an async wrapper) so stopAllProcesses
can await it, and ensure you reference stopAllProcesses, activeProcesses,
killCueProcess, entry.child and runId when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main/cue/cue-process-lifecycle.ts`:
- Around line 267-271: stopAllProcesses currently removes entries from
activeProcesses before the kill outcome is known, which can leave orphaned
processes on Windows; change stopAllProcesses to await the kill outcome and only
call activeProcesses.delete(runId) after killCueProcess(entry.child) completes
successfully, wrapping the await in a try/catch so failures are either handled
(log/record) or re-thrown per guidelines; if killCueProcess is synchronous,
convert it to return a Promise (or provide an async wrapper) so stopAllProcesses
can await it, and ensure you reference stopAllProcesses, activeProcesses,
killCueProcess, entry.child and runId when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: de426df9-9ddd-4bdf-b67e-1c2bd05045b5

📥 Commits

Reviewing files that changed from the base of the PR and between 8740ed5 and bc5ebae.

📒 Files selected for processing (2)
  • src/__tests__/main/cue/cue-executor.test.ts
  • src/main/cue/cue-process-lifecycle.ts

@chr1syy
Copy link
Copy Markdown
Contributor Author

chr1syy commented Apr 11, 2026

@pedramamini good to go for your review

@chr1syy chr1syy added the ready to merge This PR is ready to merge label Apr 11, 2026
…cesses on Windows

Async execFile('taskkill') calls were fire-and-forget — Electron exited before
taskkill.exe could finish terminating the process trees. Switch to execFileSync
during shutdown (killAll, stopAllProcesses, TunnelManager.stop) so processes are
confirmed dead before the app exits. Normal single-process kills remain async.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pedramamini pedramamini merged commit 029d2f1 into RunMaestro:rc Apr 12, 2026
3 checks passed
@chr1syy chr1syy deleted the fix/windows-closing-hang branch April 14, 2026 19:41
chr1syy added a commit to chr1syy/Maestro that referenced this pull request Apr 16, 2026
…ro#788)

* fix: use taskkill on Windows for Cue and tunnel process cleanup

Cue process lifecycle and TunnelManager used POSIX signals (SIGTERM/SIGKILL)
to terminate child processes, which don't work for shell-spawned process
trees on Windows — leaving orphaned processes after Maestro closes.

Changes:
- cue-process-lifecycle: add killCueProcess() helper using taskkill /t /f on
  Windows, falling back to SIGTERM→SIGKILL on POSIX
- cue-process-lifecycle: add stopAllProcesses() to kill all active Cue processes
- cue-executor: export stopAllCueRuns() delegating to lifecycle module
- tunnel-manager: use taskkill /t /f on Windows for cloudflared cleanup
- quit-handler: call stopAllCueRuns() during shutdown (Cue processes are
  tracked separately from ProcessManager and were never killed on quit)
- quit-handler test: mock cue-executor, verify stopAllCueRuns call order

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: handle taskkill errors explicitly and fix SIGKILL escalation test

- Add explicit error handling to taskkill callback in killCueProcess:
  ignore expected "not found" / "no running instance" errors, report
  unexpected failures to Sentry via captureException.
- Add exitCode and signalCode properties to MockChildProcess in
  cue-executor tests so the SIGKILL escalation guard condition works
  correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use synchronous taskkill during shutdown to prevent orphaned processes on Windows

Async execFile('taskkill') calls were fire-and-forget — Electron exited before
taskkill.exe could finish terminating the process trees. Switch to execFileSync
during shutdown (killAll, stopAllProcesses, TunnelManager.stop) so processes are
confirmed dead before the app exits. Normal single-process kills remain async.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready to merge This PR is ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants