Skip to content

fix(cua-driver-rs)(install): kill orphan cua-driver daemons after binary swap (install.ps1 + install-local.ps1) - #1689

Merged
f-trycua merged 3 commits into
mainfrom
kill-orphan-daemons-on-install
May 24, 2026
Merged

fix(cua-driver-rs)(install): kill orphan cua-driver daemons after binary swap (install.ps1 + install-local.ps1)#1689
f-trycua merged 3 commits into
mainfrom
kill-orphan-daemons-on-install

Conversation

@f-trycua

@f-trycua f-trycua commented May 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

After PR #1687 (`install-local.ps1` rename-out-of-way), re-installs leave the previous in-memory daemon running. It keeps serving requests + drawing its overlay window from the OLD code until reboot — which surfaces as "the bug I just fixed is still there" even though the binary on disk is the new build.

Reporter's machine on dogfood: 3 orphan `cua-driver.exe` processes accumulated from successive `install-local.ps1` + `autostart kick` rounds. Top one stuck in the topmost band (from before PR #1688's Z-order fix), drawing the cursor overlay above the user's terminal.

Fix

Both `install.ps1` (production curl-installer) and `install-local.ps1` (dev) now call a shared kill helper after the binary swap. Same function pair in both files, marked with `KEEP IN SYNC` because `install.ps1` is fetched via `irm | iex` and can't dot-source.

```powershell
function Stop-CuaDriverDaemons {
# 1. schtasks /End — SYSTEM-level kill, reaches High-IL processes
& schtasks.exe /End /TN "cua-driver-serve" 2>$null | Out-Null
# 2. taskkill /F /IM — Medium-IL backstop
& taskkill.exe /F /IM "cua-driver.exe" /T 2>$null | Out-Null
& taskkill.exe /F /IM "cua-driver-uia.exe" /T 2>$null | Out-Null
# 3. Return survivors so caller can warn
return @(Get-Process -Name "cua-driver","cua-driver-uia" -ErrorAction SilentlyContinue)
}
```

Plus `Show-CuaDriverDaemonSurvivors` prints a clear hint when a High-IL daemon (e.g. spawned by the RunLevel=Highest autostart task) survives the Medium-IL kill — pointing the user at `taskkill /IM cua-driver.exe /F` from an elevated shell.

Knock-on benefit: cua-driver update --apply

Already shells out to install.ps1, so the update path inherits this for free.

Verified end-to-end on Windows

Before: 3 cua-driver.exe orphans (pid 14152 + 2676 + 12380).

After `install-local.ps1`:

```
==> killing previous cua-driver processes (best-effort; High-IL needs admin)
Note: 1 cua-driver process(es) still running after best-effort kill (pid: 14152).
They are likely High-IL (spawned by RunLevel=Highest autostart task).
From an elevated PowerShell:
taskkill /IM cua-driver.exe /F
Or just reboot. Until they exit, the OLD binary keeps running.
```

2 of 3 orphans cleaned; the 1 High-IL survivor is correctly flagged.

Test plan

  • Both scripts pass PS parser
  • End-to-end on Windows: 3 orphans → 1 (only the High-IL autostart-spawned one survives)
  • Reviewer: run `install.ps1` on a host with multiple cua-driver instances, confirm cleanup + Note message
  • Reviewer: run `cua-driver update --apply` on Windows, confirm it inherits the cleanup via install.ps1 delegate

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Installation process now automatically stops running daemons before upgrading to ensure clean deployment of new versions.
    • Added clear user guidance when daemon processes persist after shutdown attempts.
    • Installation scripts now support both local and remote execution scenarios while maintaining daemon management.

Review Change Stack

…ary swap (install.ps1 + install-local.ps1)

User report after PR #1687 (rename-out-of-way): re-installing leaves
the previous in-memory daemon running. That daemon keeps serving
requests + drawing its overlay window from the OLD code until reboot.
Surfaces as "the bug I just fixed is still there" because the binary
on disk is new but the running process is pre-fix. Repro on the
reporter's machine had THREE orphan cua-driver.exe processes
accumulated from successive install-local.ps1 + autostart kick runs.

Two scripts get the same fix, shared via a duplicated function pair
(install.ps1 is fetched via `irm | iex` so it can't dot-source — the
duplication is intentional, marked with KEEP IN SYNC markers):

- Stop-CuaDriverDaemons: schtasks /End → taskkill /F /IM → returns
  any process that survived (those need elevation to kill).
- Show-CuaDriverDaemonSurvivors: prints a hint pointing the user at
  an elevated `taskkill` one-liner.

install.ps1 runs the pair right before the autostart-registration
block. install-local.ps1 runs it after the binary rename, replacing
the inline block that PR #1687 added (functionally identical, just
factored out for the share-the-logic story).

Knock-on: because `cua-driver update --apply` invokes install.ps1
(production curl-installer) under the hood, the update path inherits
this behaviour for free.

Verified end-to-end on Windows with 3 orphan daemons + 1 High-IL
daemon from the autostart task: 2 of 3 die cleanly (Medium-IL kill
works), 1 survives + the Note hint fires pointing the user at
`taskkill /IM cua-driver.exe /F` from an elevated shell.
@vercel

vercel Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview May 24, 2026 3:18pm

Request Review

…uaDriverInstall.psm1 (true single source)

PR feedback on #1689: "use a psm to share" instead of the
KEEP-IN-SYNC duplication.

New module at libs/cua-driver/scripts/CuaDriverInstall.psm1 owns:
  - Stop-CuaDriverDaemons (schtasks /End -> taskkill /F /IM -> return
    survivors)
  - Show-CuaDriverDaemonSurvivors (warn + hint on Medium-IL kill that
    couldn't reach a High-IL daemon)
  - Import-CuaDriverInstallModule (helper for picking on-disk vs
    network load; used by install.ps1's bootstrap)

Two load paths:

  install-local.ps1 - checked-out tree, .psm1 is right there on disk
                      next to it. One liner: Import-Module
                      $ScriptDir/CuaDriverInstall.psm1 -Force.

  install.ps1       - fetched via `irm | iex`, no file on disk.
                      Inline bootstrap function tries on-disk first
                      ($PSScriptRoot, populated when run from a
                      checked-out tree / .ps1 file on disk), then
                      falls back to Invoke-RestMethod against the
                      same .psm1 on GitHub raw + Import-Module from
                      a temp file. Module stays in memory after the
                      temp file is removed.

End-to-end verified on Windows:

  ==> killing previous cua-driver processes (best-effort; High-IL needs admin)
  Note: 1 cua-driver process(es) still running after best-effort kill (pid: 14152).
        They are likely High-IL (spawned by RunLevel=Highest autostart task).
        From an elevated PowerShell:
          taskkill /IM cua-driver.exe /F
        Or just reboot. Until they exit, the OLD binary keeps running.

Two-on-three orphan daemons cleaned (Medium-IL); the High-IL
autostart-spawned survivor correctly reported. Same behaviour as the
duplicate-in-both-files version from PR #1689 but now there's a
single canonical definition.
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55995731-7ba0-4e1d-be89-5e40ea7bd0c7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR extracts daemon-cleanup logic into a shared PowerShell module (CuaDriverInstall.psm1), then integrates it into both local and remote Windows installers to ensure running cua-driver daemons are cleanly stopped before or after binary updates.

Changes

Daemon Cleanup Refactoring

Layer / File(s) Summary
Shared daemon management module
libs/cua-driver/scripts/CuaDriverInstall.psm1
New module exports three functions: Stop-CuaDriverDaemons (terminates scheduled tasks and processes, returns survivors), Show-CuaDriverDaemonSurvivors (prints remediation guidance), and Import-CuaDriverInstallModule (loads module locally or fetches from remote URL into temp file).
Local installer daemon cleanup integration
libs/cua-driver/scripts/install-local.ps1
Imports shared module and calls Stop-CuaDriverDaemons during previous-install cleanup, logging any surviving processes before new binary staging.
Remote installer bootstrap and daemon cleanup
libs/cua-driver/scripts/install.ps1
Adds Import-CuaDriverInstallModuleBootstrap helper for local or remote module loading (supports irm | iex execution); post-install, stops running daemons and reports survivors before autostart registration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • trycua/cua#1649: Changes Windows installer daemon shutdown behavior using scheduled-task termination plus process fallback.
  • trycua/cua#1673: Rust updater delegates to install.ps1 one-liner, which now bootstraps and invokes daemon shutdown via the shared module.
  • trycua/cua#1644: Overlaps on daemon-shutdown code paths during install flow.

Poem

🐰 A module born to tame the daemons wild,
Shared across installers, neat and filed.
Processes stop, survivors report true,
Fresh binaries run—old ghosts bid adieu!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: killing orphan cua-driver daemons after binary swap during installation, and specifically mentions both affected scripts (install.ps1 + install-local.ps1).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kill-orphan-daemons-on-install

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.

…install-common.sh (matches PowerShell .psm1 pattern)

PR #1689 just extracted the equivalent Windows logic into
CuaDriverInstall.psm1. Same single-source-of-truth treatment for the
sh installers — and rename the PowerShell module to _install-common.psm1
so the two halves share the underscore-prefixed `internal helper`
naming convention used by the other _install-*.{sh,ps1} files.

New shared file: libs/cua-driver/scripts/_install-common.sh
Functions: stop_cua_driver_daemons, show_cua_driver_daemon_survivors.
Loaded from on-disk first (when run from a checked-out tree) or from
GitHub raw via curl when install.sh runs as `curl ... | bash`.

Consumers updated to source it:
  - _install-rust.sh           (production Rust delegate; on-disk +
                                network fallback)
  - _install-local-rust.sh     (dev-build Rust installer; on-disk only)
  - _install-local-swift.sh    (dev-build Swift installer; same kill
                                covers both since both binaries bake
                                the `cua-driver` name)

macOS LaunchAgent plist names (verified from _install-local-*.sh):
  - Rust:  ~/Library/LaunchAgents/com.trycua.cua-driver-rs.plist
  - Swift: ~/Library/LaunchAgents/com.trycua.cua_driver_daemon.plist
Linux systemd unit (verified from _install-local-rust.sh):
  - cua-driver-rs.service

One pkill -x cua-driver covers both Rust and Swift binaries since both
exec under the same product name; no need for a per-backend kill
variant. cua-driver-uia is Windows-only so it never appears on Unix
hosts and isn't probed here.

bash -n passes on all touched files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@f-trycua
f-trycua merged commit 5ad4cfe into main May 24, 2026
6 of 7 checks passed
@f-trycua
f-trycua deleted the kill-orphan-daemons-on-install branch May 24, 2026 15:21
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.

1 participant