Skip to content

feat(cua-driver): remove LaunchAgent auto-updater; add version hint and launch_app improvements - #1388

Merged
f-trycua merged 2 commits into
mainfrom
feat/cua-driver-updater-and-launch-improvements
Apr 26, 2026
Merged

feat(cua-driver): remove LaunchAgent auto-updater; add version hint and launch_app improvements#1388
f-trycua merged 2 commits into
mainfrom
feat/cua-driver-updater-and-launch-improvements

Conversation

@f-trycua

@f-trycua f-trycua commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Auto-updater removal

The weekly LaunchAgent (com.trycua.cua_driver_updater) silently replaced a security-sensitive binary (accessibility + Apple Events entitlements) on a schedule. This is disruptive and raises concerns for users on managed machines.

New model: passive version hint on startup + explicit cua-driver update [--apply] command.

  • Server startup prints to stderr if a newer release exists (background task, 4s timeout, silent if offline)
  • cua-driver update shows what's available; cua-driver update --apply runs install.sh
  • uninstall.sh cleans up the legacy LaunchAgent from existing installs

launch_app improvements (closes #1377)

Two new params enable isolated browser sessions for multi-agent testing:

{
  "bundle_id": "com.google.Chrome",
  "urls": ["http://localhost:3000"],
  "creates_new_application_instance": true,
  "additional_arguments": [
    "--user-data-dir=/tmp/cua-session-a",
    "--no-first-run",
    "--no-default-browser-check"
  ]
}

Each session gets a distinct pid, isolated profile (no shared cookies/localStorage/extensions), and can be controlled independently. The user's real Chrome profile is untouched.

Test plan

  • swift build passes
  • cua-driver update shows version info without applying
  • cua-driver update --apply triggers install.sh
  • launch_app with creates_new_application_instance=true + additional_arguments launches isolated Chrome

Closes #1377

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Version checking now alerts users when updates are available.
    • App launching extended to support debugging ports and WebKit inspection configuration.
  • Changes

    • Update command now fetches releases from GitHub with new --apply flag for automatic installation; without it, users receive manual update guidance.
    • Automatic updater removed from installation process.

@vercel

vercel Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Apr 26, 2026 7:48am

Request Review

@coderabbitai

coderabbitai Bot commented Apr 26, 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: 2ed8a529-e49f-422c-bde9-b947e2f8a097

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

The pull request refactors the cua-driver update mechanism to fetch version information from GitHub instead of relying on a local auto-update script, and extends the app launcher to support isolated browser sessions through configurable command-line arguments, environment variables, and instance creation flags.

Changes

Cohort / File(s) Summary
Update System Refactor
CuaDriverCommand.swift, VersionCheck.swift, ServeCommand.swift, install.sh, uninstall.sh
Replaces local auto-update script with GitHub-based version checking; UpdateCommand now fetches latest release via VersionCheck.fetchLatest(), compares versions with isNewer(), and conditionally runs install.sh via --apply flag; ServeCommand adds non-blocking background version warning; installer removes auto-updater setup; uninstall script updates legacy artifact cleanup.
App Launch Enhancement
AppLauncher.swift, LaunchAppTool.swift
Extends AppLauncher.launch() to accept optional additionalArguments, additionalEnvironment, and createsNewApplicationInstance; LaunchAppTool now configures Electron debugging port, WebKit inspector port, and process identity controls, passing them through to the enhanced launcher for isolated browser session support.

Sequence Diagram(s)

sequenceDiagram
    actor User as User
    participant CLI as UpdateCommand
    participant VC as VersionCheck
    participant GitHub as GitHub API
    participant Script as install.sh
    
    User->>CLI: Run with/without --apply
    CLI->>CLI: Output current version
    CLI->>VC: fetchLatest()
    VC->>GitHub: GET releases (cua-driver-v*)
    GitHub-->>VC: Latest release tag
    VC-->>CLI: Version string
    CLI->>VC: isNewer(latest, current)
    VC-->>CLI: Boolean result
    alt Already up to date
        CLI-->>User: "Already up to date"
    else Update available
        alt --apply flag provided
            CLI->>Script: bash install.sh (execute)
            Script-->>CLI: Exit code
            CLI-->>User: Install complete or error
        else Manual update guidance
            CLI-->>User: Show manual curl | bash command
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • #1359 — Introduces the initial cua-driver CLI commands (UpdateCommand, ServeCommand), AppLauncher, and LaunchAppTool that are being refactored and extended in this PR.

Poem

🐰 The driver learns to self-improve via GitHub's distant stars,
While browsers spawn in isolation, each with unique memoirs,
No dusty scripts clutter the attic, just elegant checks—
One hop toward safety, one bound toward control! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main changes: removing the LaunchAgent auto-updater, adding a version check hint, and enhancing the launch_app tool with new parameters.
Linked Issues check ✅ Passed The PR fulfills issue #1377 requirements: launch_app now accepts creates_new_application_instance, additional_arguments, and additional_environment to enable isolated browser sessions with distinct PIDs and separate user-data directories, matching all stated acceptance criteria.
Out of Scope Changes check ✅ Passed All changes are directly in scope: the auto-updater removal and version check align with the passive update model in the PR objectives; launch_app enhancements directly address issue #1377; install/uninstall script changes support the updater removal.

✏️ 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 feat/cua-driver-updater-and-launch-improvements

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift (3)

345-352: Diagnostics go to stdout instead of stderr.

"Could not reach GitHub …" and "Installation failed …" (line 375) are user-facing errors but print writes to stdout, mixing them with normal "Already up to date." output. Callers piping cua-driver update into another tool can't distinguish success from failure by stream alone. Consider routing the error lines through FileHandle.standardError.write(...) like the rest of the CLI does.

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

In `@libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift` around lines 345
- 352, The guard that uses VersionCheck.fetchLatest() prints errors to stdout
with print(...) — change those error messages (the "Could not reach GitHub —
check your connection and try again." and the later "Installation failed …") to
write to stderr instead by sending their UTF-8 Data to FileHandle.standardError
(e.g. replace print(...) with FileHandle.standardError.write((message +
"\n").data(using: .utf8)!)) in the CuaDriverCommand flow where
VersionCheck.fetchLatest() and the installation failure path are handled so
callers can distinguish errors via stderr.

372-377: Process.run() not error-handled; orphaned autoUpdateEnabled config field.

Two small follow-ups on the install path:

  1. try proc.run() will throw straight out of the function if /bin/bash is somehow unavailable or the working-directory state rejects spawning. The user sees a raw NSError rather than a friendly hint. Wrapping in do { try proc.run() } catch { … throw ExitCode(1) } mirrors the diagnostic style used in ServeCommand.relaunchViaOpen.

  2. CuaDriverConfig.autoUpdateEnabled and ConfigStore.setAutoUpdateEnabledSync (referenced in the relevant snippets) are still wired into ConfigCommand, but UpdateCommand no longer reads them. A user toggling cua-driver config updates disable will silently have no effect on this passive-update model. Either remove the field + subcommand or repurpose it to silence VersionCheck.warnIfOutdated() on serve startup so the toggle actually means something.

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

In `@libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift` around lines 372
- 377, Wrap the call to proc.run() in a do/catch and handle errors similarly to
ServeCommand.relaunchViaOpen: attempt try proc.run() inside do, catch the error,
print a friendly diagnostic message and throw ExitCode(1) so users see a clear
hint instead of an unhandled NSError; additionally, reconcile the orphaned
auto-update config by either removing CuaDriverConfig.autoUpdateEnabled and
ConfigStore.setAutoUpdateEnabledSync (and the related ConfigCommand subcommand)
or by repurposing them so UpdateCommand/serve startup consults the
flag—specifically wire the flag into VersionCheck.warnIfOutdated() (or guard
that call during serve startup) so toggling via ConfigCommand actually
suppresses warnings as intended.

376-376: Redundant Int32 cast on terminationStatus.

Process.terminationStatus is already Int32, so Int32(proc.terminationStatus) is a no-op.

-            throw ExitCode(Int32(proc.terminationStatus))
+            throw ExitCode(proc.terminationStatus)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift` at line 376, The
throw line uses a redundant Int32 cast — replace the unnecessary
Int32(proc.terminationStatus) with proc.terminationStatus directly when
constructing ExitCode (i.e., change the throw in CuaDriverCommand where proc is
used to pass proc.terminationStatus into ExitCode), removing the no-op cast to
simplify the code.
libs/cua-driver/Sources/CuaDriverCLI/VersionCheck.swift (2)

11-32: Lookup is fragile if 40 non-driver releases land first.

fetchLatest walks the first page (per_page=40) and returns the first non-draft / non-prerelease release whose tag starts with cua-driver-v. If the trycua/cua monorepo ever ships ≥40 newer releases for sibling components (cua-cli-v*, cua-server-v*, …) before the next cua-driver release, the loop falls off the end and fetchLatest() silently returns nil, suppressing the upgrade hint until the next driver release rolls onto page 1.

Consider either filtering the URL by tag prefix on the server (e.g. via the git/refs/tags/cua-driver-v API) or following pagination until a match is found. Low-priority for now since cua-driver is currently the dominant release line, but worth noting.

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

In `@libs/cua-driver/Sources/CuaDriverCLI/VersionCheck.swift` around lines 11 -
32, fetchLatest currently only checks a single page (per_page=40) which can miss
the newest cua-driver tag if ≥40 non-driver tags precede it; update fetchLatest
to either (a) query the GitHub tags ref API for the driver prefix (e.g. use the
git/refs/tags endpoint filtered by tagPrefix) or (b) implement pagination: add a
page parameter to the request URL, loop requests using the Link header (or
increment page until no results) and inspect each page’s releases for the first
non-draft/non-prerelease tag starting with tagPrefix; update references to repo,
tagPrefix and the URLRequest/URLSession logic in fetchLatest to continue paging
until a match is found or no more pages remain.

46-57: Detached background task may be torn down before it can warn.

warnIfOutdated() fires Task.detached and returns immediately. For short-lived CLI invocations (e.g. cua-driver list_apps if it ever calls this) the parent process can exit before the 4-second HTTP fetch resolves, leaving the user with no warning. Today only serve calls this and the daemon outlives the request, so it's fine — but if you ever wire it into other entry points, you'll want either to await the task or to drop the warning entirely on short-lived paths.

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

In `@libs/cua-driver/Sources/CuaDriverCLI/VersionCheck.swift` around lines 46 -
57, warnIfOutdated currently spawns a Task.detached that may be torn down if the
process exits; change warnIfOutdated to return a Task<Void, Never> created with
Task { ... } (not Task.detached) so callers can await it on short‑lived CLI
paths, or modify callers to await the returned Task; update references to
warnIfOutdated, fetchLatest, isNewer, and CuaDriverCore.version so the new Task
handle is awaited where appropriate (e.g., short-lived commands) while
long‑running commands like serve can ignore the handle.
libs/cua-driver/Sources/CuaDriverServer/Tools/LaunchAppTool.swift (1)

148-149: Consider validating port ranges for electron_debugging_port and webkit_inspector_port.

intValue accepts any integer — negative values, 0, or values above 65535 will be passed through verbatim into --remote-debugging-port=<N> or WEBKIT_INSPECTOR_SERVER=127.0.0.1:<N>. Chrome will silently fail to start CDP on an invalid port, leaving the caller to discover the misconfiguration via tool failure later. A quick (1...65535).contains(port) check at the boundary would surface a clear error.

Also applies to: 207-219

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

In `@libs/cua-driver/Sources/CuaDriverServer/Tools/LaunchAppTool.swift` around
lines 148 - 149, Validate parsed port integers for electronDebuggingPort and
webkitInspectorPort by ensuring any non-nil port falls within the valid TCP port
range 1...65535 before using them; if a port is out of range, return or throw a
clear, descriptive error rather than passing the invalid value into
"--remote-debugging-port" or "WEBKIT_INSPECTOR_SERVER". Apply the same boundary
check to the other port-parsing sites in this file where similar variables are
created/used (the other electron/webkit port handling blocks) so all uses of
electronDebuggingPort and webkitInspectorPort (and their siblings) perform the
(1...65535).contains(port) check and produce a user-visible error when invalid.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift`:
- Around line 337-338: Add a hidden deprecated alias for the removed --silent
flag and fold it into the existing apply logic: introduce a new `@Flag` annotated
variable (e.g., `@Flag`(name: .long, help: .hidden) var silent = false) alongside
the existing var apply, then at the top of run() compute a single boolean (e.g.,
let shouldApply = apply || silent) and use shouldApply everywhere instead of
apply; reference the existing apply var and the run() method when making the
change.

In `@libs/cua-driver/Sources/CuaDriverCLI/VersionCheck.swift`:
- Around line 35-42: The isNewer(_ candidate:than:) function currently treats
versions with extra trailing zero components as newer (e.g., "1.2.0" > "1.2");
update isNewer to normalize both versions before comparing by either stripping
trailing zeros from the arrays returned by parts(_:) or by extending the shorter
array with zeros so that semantic equivalents compare equal. Locate isNewer and
its use of parts(candidate)/parts(current) and adjust normalization (remove
trailing zero entries or pad with zeros) so zipped comparisons correctly treat
"1.2" and "1.2.0" as equal and only return true for truly larger versions.

In `@libs/cua-driver/Sources/CuaDriverCore/Apps/AppLauncher.swift`:
- Around line 67-69: The code assigns additionalEnvironment directly to
config.environment which replaces the inherited process environment and drops
critical vars; change the logic in AppLauncher (the block that checks
additionalEnvironment) to merge additionalEnvironment into
ProcessInfo.processInfo.environment (e.g., start from
ProcessInfo.processInfo.environment, overlay additionalEnvironment) and then
assign that merged dictionary to config.environment so inherited vars like
PATH/HOME/LANG are preserved.

In `@libs/cua-driver/Sources/CuaDriverServer/Tools/LaunchAppTool.swift`:
- Line 206: The code silently drops malformed entries from rawExtraArgs when
building additionalArguments (var additionalArguments: [String] =
rawExtraArgs.compactMap { $0.stringValue }), causing inconsistent validation vs.
rawUrls and potential security footguns; change this to validate each
rawExtraArgs element the same way rawUrls does: iterate rawExtraArgs, require
each has a non-empty stringValue, and on any invalid/missing entry return the
same errorResult used for rawUrls (include context about additional_arguments)
instead of silently compact-mapping; update the function that processes
additionalArguments to use this explicit validation and the same error handling
path as rawUrls.

---

Nitpick comments:
In `@libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift`:
- Around line 345-352: The guard that uses VersionCheck.fetchLatest() prints
errors to stdout with print(...) — change those error messages (the "Could not
reach GitHub — check your connection and try again." and the later "Installation
failed …") to write to stderr instead by sending their UTF-8 Data to
FileHandle.standardError (e.g. replace print(...) with
FileHandle.standardError.write((message + "\n").data(using: .utf8)!)) in the
CuaDriverCommand flow where VersionCheck.fetchLatest() and the installation
failure path are handled so callers can distinguish errors via stderr.
- Around line 372-377: Wrap the call to proc.run() in a do/catch and handle
errors similarly to ServeCommand.relaunchViaOpen: attempt try proc.run() inside
do, catch the error, print a friendly diagnostic message and throw ExitCode(1)
so users see a clear hint instead of an unhandled NSError; additionally,
reconcile the orphaned auto-update config by either removing
CuaDriverConfig.autoUpdateEnabled and ConfigStore.setAutoUpdateEnabledSync (and
the related ConfigCommand subcommand) or by repurposing them so
UpdateCommand/serve startup consults the flag—specifically wire the flag into
VersionCheck.warnIfOutdated() (or guard that call during serve startup) so
toggling via ConfigCommand actually suppresses warnings as intended.
- Line 376: The throw line uses a redundant Int32 cast — replace the unnecessary
Int32(proc.terminationStatus) with proc.terminationStatus directly when
constructing ExitCode (i.e., change the throw in CuaDriverCommand where proc is
used to pass proc.terminationStatus into ExitCode), removing the no-op cast to
simplify the code.

In `@libs/cua-driver/Sources/CuaDriverCLI/VersionCheck.swift`:
- Around line 11-32: fetchLatest currently only checks a single page
(per_page=40) which can miss the newest cua-driver tag if ≥40 non-driver tags
precede it; update fetchLatest to either (a) query the GitHub tags ref API for
the driver prefix (e.g. use the git/refs/tags endpoint filtered by tagPrefix) or
(b) implement pagination: add a page parameter to the request URL, loop requests
using the Link header (or increment page until no results) and inspect each
page’s releases for the first non-draft/non-prerelease tag starting with
tagPrefix; update references to repo, tagPrefix and the URLRequest/URLSession
logic in fetchLatest to continue paging until a match is found or no more pages
remain.
- Around line 46-57: warnIfOutdated currently spawns a Task.detached that may be
torn down if the process exits; change warnIfOutdated to return a Task<Void,
Never> created with Task { ... } (not Task.detached) so callers can await it on
short‑lived CLI paths, or modify callers to await the returned Task; update
references to warnIfOutdated, fetchLatest, isNewer, and CuaDriverCore.version so
the new Task handle is awaited where appropriate (e.g., short-lived commands)
while long‑running commands like serve can ignore the handle.

In `@libs/cua-driver/Sources/CuaDriverServer/Tools/LaunchAppTool.swift`:
- Around line 148-149: Validate parsed port integers for electronDebuggingPort
and webkitInspectorPort by ensuring any non-nil port falls within the valid TCP
port range 1...65535 before using them; if a port is out of range, return or
throw a clear, descriptive error rather than passing the invalid value into
"--remote-debugging-port" or "WEBKIT_INSPECTOR_SERVER". Apply the same boundary
check to the other port-parsing sites in this file where similar variables are
created/used (the other electron/webkit port handling blocks) so all uses of
electronDebuggingPort and webkitInspectorPort (and their siblings) perform the
(1...65535).contains(port) check and produce a user-visible error when invalid.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: abdccfcd-c410-4b34-8936-bf20d3ac7d2d

📥 Commits

Reviewing files that changed from the base of the PR and between 66bed0d and 2570077.

📒 Files selected for processing (7)
  • libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift
  • libs/cua-driver/Sources/CuaDriverCLI/ServeCommand.swift
  • libs/cua-driver/Sources/CuaDriverCLI/VersionCheck.swift
  • libs/cua-driver/Sources/CuaDriverCore/Apps/AppLauncher.swift
  • libs/cua-driver/Sources/CuaDriverServer/Tools/LaunchAppTool.swift
  • libs/cua-driver/scripts/install.sh
  • libs/cua-driver/scripts/uninstall.sh

Comment on lines +337 to +338
@Flag(name: .long, help: "Download and apply the update without prompting.")
var apply = false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether the prior --silent flag ever shipped in a tagged release.
git log --all --oneline -- libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift | head -20
git tag --list 'cua-driver-v*' | sort -V | tail -5

Repository: trycua/cua

Length of output: 217


🏁 Script executed:

#!/bin/bash
# Check what the file looked like at v0.1 release
git show 9bb952c6:libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift 2>/dev/null | grep -A 2 -B 2 "silent\|apply" || echo "File not found at that commit or grep found nothing"

# List all tags more carefully
echo "=== All cua-driver tags ==="
git tag -l | grep -i "cua-driver"

# Check git log for when --silent was removed
echo "=== Commits mentioning silent ==="
git log --all --oneline --grep="silent" -- libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift 2>/dev/null || echo "No commits found with 'silent' in message"

# Check git log for when --apply was added
echo "=== Commits mentioning apply ==="
git log --all --oneline --grep="apply" -- libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift 2>/dev/null || echo "No commits found with 'apply' in message"

# Try to find when silent flag was removed from the file
echo "=== Checking for silent in file history ==="
git log -p --all -- libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift 2>/dev/null | grep -B 5 -A 5 "silent" | head -40 || echo "No silent flag found in history"

Repository: trycua/cua

Length of output: 1269


🏁 Script executed:

#!/bin/bash
# Check the change in commit 25700779
echo "=== Full diff for commit 25700779 ==="
git show 25700779 -- libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift | grep -A 10 -B 10 "silent\|apply" | head -60

# Check current HEAD
echo ""
echo "=== Current HEAD for the file (lines 330-345) ==="
git show HEAD:libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift 2>/dev/null | sed -n '330,345p'

# Check if 25700779 is the current HEAD
echo ""
echo "=== Current HEAD commit ==="
git rev-parse HEAD

Repository: trycua/cua

Length of output: 3200


Breaking CLI change: --silent--apply with no alias.

The --silent flag shipped in the v0.1 public release and is now removed entirely. Any user scripts or documentation calling cua-driver update --silent will start failing argument parsing. Keep --silent as a deprecated hidden alias for one release cycle:

    `@Flag`(name: .long, help: "Download and apply the update without prompting.")
    var apply = false
+
+    // Deprecated alias for one release; remove after 0.0.7.
+    `@Flag`(name: [.customLong("silent")], help: .hidden)
+    var silentDeprecated = false

Then OR the two together at the top of run().

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Flag(name: .long, help: "Download and apply the update without prompting.")
var apply = false
`@Flag`(name: .long, help: "Download and apply the update without prompting.")
var apply = false
// Deprecated alias for one release; remove after 0.0.7.
`@Flag`(name: [.customLong("silent")], help: .hidden)
var silentDeprecated = false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/cua-driver/Sources/CuaDriverCLI/CuaDriverCommand.swift` around lines 337
- 338, Add a hidden deprecated alias for the removed --silent flag and fold it
into the existing apply logic: introduce a new `@Flag` annotated variable (e.g.,
`@Flag`(name: .long, help: .hidden) var silent = false) alongside the existing var
apply, then at the top of run() compute a single boolean (e.g., let shouldApply
= apply || silent) and use shouldApply everywhere instead of apply; reference
the existing apply var and the run() method when making the change.

Comment on lines +35 to +42
static func isNewer(_ candidate: String, than current: String) -> Bool {
let lhs = parts(candidate)
let rhs = parts(current)
for (a, b) in zip(lhs, rhs) {
if a != b { return a > b }
}
return lhs.count > rhs.count
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

isNewer returns true for equal versions written with trailing-zero padding.

When lhs and rhs agree on every zipped component but lhs has trailing extra parts, the function returns true. Concretely:

  • isNewer("1.2.0", than: "1.2")lhs=[1,2,0], rhs=[1,2], zip is all equal, then lhs.count > rhs.count → returns true, even though the versions are semantically equivalent.

In practice the cua-driver tag format is fixed three-part X.Y.Z so this isn't currently exploitable, but if a future tag is normalized differently (e.g. 0.1 vs 0.1.0), startup warnIfOutdated() would noisily warn forever. Strip trailing zeros before comparing, or treat missing parts as 0 on both sides.

♻️ Possible fix
     static func isNewer(_ candidate: String, than current: String) -> Bool {
         let lhs = parts(candidate)
         let rhs = parts(current)
-        for (a, b) in zip(lhs, rhs) {
-            if a != b { return a > b }
+        let count = max(lhs.count, rhs.count)
+        for i in 0..<count {
+            let a = i < lhs.count ? lhs[i] : 0
+            let b = i < rhs.count ? rhs[i] : 0
+            if a != b { return a > b }
         }
-        return lhs.count > rhs.count
+        return false
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static func isNewer(_ candidate: String, than current: String) -> Bool {
let lhs = parts(candidate)
let rhs = parts(current)
for (a, b) in zip(lhs, rhs) {
if a != b { return a > b }
}
return lhs.count > rhs.count
}
static func isNewer(_ candidate: String, than current: String) -> Bool {
let lhs = parts(candidate)
let rhs = parts(current)
let count = max(lhs.count, rhs.count)
for i in 0..<count {
let a = i < lhs.count ? lhs[i] : 0
let b = i < rhs.count ? rhs[i] : 0
if a != b { return a > b }
}
return false
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/cua-driver/Sources/CuaDriverCLI/VersionCheck.swift` around lines 35 -
42, The isNewer(_ candidate:than:) function currently treats versions with extra
trailing zero components as newer (e.g., "1.2.0" > "1.2"); update isNewer to
normalize both versions before comparing by either stripping trailing zeros from
the arrays returned by parts(_:) or by extending the shorter array with zeros so
that semantic equivalents compare equal. Locate isNewer and its use of
parts(candidate)/parts(current) and adjust normalization (remove trailing zero
entries or pad with zeros) so zipped comparisons correctly treat "1.2" and
"1.2.0" as equal and only return true for truly larger versions.

Comment on lines +67 to +69
if !additionalEnvironment.isEmpty {
config.environment = additionalEnvironment
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

NSWorkspace.OpenConfiguration environment property replace or merge with inherited environment macOS

💡 Result:

The environment property of NSWorkspace.OpenConfiguration sets the environment variables for the newly launched app instance. Apple documentation describes it as "The set of environment variables to set in a new app instance." This indicates that the dictionary provided completely replaces the default environment (inherited from launchd/system defaults) with exactly the key-value pairs specified, rather than merging or augmenting the inherited environment. No official documentation explicitly states "replace" vs "merge," but the phrasing "to set" combined with standard macOS launch behaviors (where launchd provides a minimal/default environment, and tools like NSTask allow full replacement via dictionary) supports replacement semantics. Sandboxed callers have restrictions on modifying arguments and environment, but the property is available. Example usage: let config = NSWorkspace.OpenConfiguration config.environment = ["MY_VAR": "value", "PATH": "/custom/path:$PATH"] NSWorkspace.shared.openApplication(at: appURL, configuration: config) { app, error in ... } Note: To preserve standard vars like PATH, explicitly include them in the dictionary.

Citations:


Environment replacement will strip critical inherited variables; merge with ProcessInfo.processInfo.environment before assigning.

Setting config.environment to a small dictionary like ["WEBKIT_INSPECTOR_SERVER": ..., "TAURI_WEBVIEW_AUTOMATION": "1"] completely replaces the inherited environment. This removes critical variables (PATH, HOME, LANG, etc.) that the launched app depends on, breaking Tauri/WKWebView launches when only webkit_inspector_port is set.

Merge additionalEnvironment onto ProcessInfo.processInfo.environment before assignment, or if intentional, document this behavior loudly.

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

In `@libs/cua-driver/Sources/CuaDriverCore/Apps/AppLauncher.swift` around lines 67
- 69, The code assigns additionalEnvironment directly to config.environment
which replaces the inherited process environment and drops critical vars; change
the logic in AppLauncher (the block that checks additionalEnvironment) to merge
additionalEnvironment into ProcessInfo.processInfo.environment (e.g., start from
ProcessInfo.processInfo.environment, overlay additionalEnvironment) and then
assign that merged dictionary to config.environment so inherited vars like
PATH/HOME/LANG are preserved.

)
}

var additionalArguments: [String] = rawExtraArgs.compactMap { $0.stringValue }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent input validation: silent drop of malformed additional_arguments entries.

rawUrls rejects non-string / empty entries with an errorResult (lines 163-171), but rawExtraArgs uses compactMap { $0.stringValue } and silently drops anything that isn't a string. A caller passing additional_arguments: ["--user-data-dir=...", 42] would get a partial argv with no warning, which is exactly the kind of "isolated session is suddenly using the user's real profile" footgun #1377 wants to avoid.

🛡️ Suggested validation parity with `urls`
-                var additionalArguments: [String] = rawExtraArgs.compactMap { $0.stringValue }
+                var additionalArguments: [String] = []
+                for raw in rawExtraArgs {
+                    guard let str = raw.stringValue else {
+                        return errorResult("additional_arguments entries must be strings.")
+                    }
+                    additionalArguments.append(str)
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var additionalArguments: [String] = rawExtraArgs.compactMap { $0.stringValue }
var additionalArguments: [String] = []
for raw in rawExtraArgs {
guard let str = raw.stringValue else {
return errorResult("additional_arguments entries must be strings.")
}
additionalArguments.append(str)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/cua-driver/Sources/CuaDriverServer/Tools/LaunchAppTool.swift` at line
206, The code silently drops malformed entries from rawExtraArgs when building
additionalArguments (var additionalArguments: [String] = rawExtraArgs.compactMap
{ $0.stringValue }), causing inconsistent validation vs. rawUrls and potential
security footguns; change this to validate each rawExtraArgs element the same
way rawUrls does: iterate rawExtraArgs, require each has a non-empty
stringValue, and on any invalid/missing entry return the same errorResult used
for rawUrls (include context about additional_arguments) instead of silently
compact-mapping; update the function that processes additionalArguments to use
this explicit validation and the same error handling path as rawUrls.

f-trycua and others added 2 commits April 26, 2026 00:46
…nd launch_app improvements

Auto-updater (LaunchAgent):
- Remove com.trycua.cua_driver_updater LaunchAgent from install.sh —
  weekly silent binary replacement of a security-sensitive tool is
  disruptive and raises MDM/security concerns
- uninstall.sh gracefully removes the LaunchAgent from existing installs
- `cua-driver update` simplified: checks GitHub, prints instructions,
  applies with --apply flag by running install.sh
- VersionCheck: lightweight GitHub releases check on server startup;
  prints one-liner to stderr if a newer version exists (fire-and-forget,
  4s timeout, silent if offline)

launch_app improvements (closes #1377):
- `creates_new_application_instance`: force a new process even if the
  app is already running — enables isolated Chrome sessions per agent
- `additional_arguments`: raw argv passthrough for any launch flag
  (e.g. --user-data-dir=/tmp/session-a for isolated Chrome profiles)
- AppLauncher: wire both through NSWorkspace.OpenConfiguration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ent in AppLauncher

Setting config.environment to only additionalEnvironment replaced the
entire inherited environment, stripping PATH, HOME, LANG and similar
variables that launched apps expect. Merge additionalEnvironment on top
of ProcessInfo.processInfo.environment instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

cua-driver: support isolated background browser sessions for multi-agent testing

1 participant