Skip to content

cua-driver: fix #1481 app name resolution — bundle ID + locale fallbacks - #1492

Merged
f-trycua merged 2 commits into
mainfrom
cua-driver/fix-1481-upstream
May 16, 2026
Merged

cua-driver: fix #1481 app name resolution — bundle ID + locale fallbacks#1492
f-trycua merged 2 commits into
mainfrom
cua-driver/fix-1481-upstream

Conversation

@ddupont808

@ddupont808 ddupont808 commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1481launch_app name= now resolves beyond the exact bundle filename.

Root cause: AppLauncher.locate() only did a filesystem lookup for <name>.app. Callers who passed a bundle ID string ("com.apple.calculator") or a locale-specific display name ("計算機" on JP macOS) got a silent "not found" error.

Fix: Three-pass fallback chain in AppLauncher.locate():

  1. Filesystem lookup (existing — fast) — <name>.app in standard directories
  2. LaunchServices bundle-ID lookupNSWorkspace.urlForApplication(withBundleIdentifier: name) for callers who pass "com.apple.calculator" as name
  3. Fuzzy scan across installed bundles:
    • NSRunningApplication.localizedName (locale-aware — matches "計算機" on JP macOS)
    • CFBundleDisplayName / CFBundleName from Info.plist (English; case-insensitive)
    • Bundle URL stem (filename minus .app)

Before / After

# OLD binary
launch_app name="com.apple.calculator" → Error: Could not locate app (name 'com.apple.calculator').
launch_app name="calculator"           → Error: Could not locate app (name 'calculator').

# NEW binary
launch_app name="com.apple.calculator" → pid=…, bundle_id=com.apple.calculator ✓
launch_app name="calculator"           → pid=…, bundle_id=com.apple.calculator ✓
launch_app name="CALCULATOR"           → pid=…, bundle_id=com.apple.calculator ✓
launch_app name="Calculator"           → pid=…, bundle_id=com.apple.calculator ✓ (unchanged)

Issue #1491 (mcp TCC)

Already fixed in PR #1479 (41c6afdf). cua-driver mcp --help confirms --no-daemon-relaunch and the auto-daemon-launch are present on current main. No code changes needed.

Test plan

  • 5 new integration tests in test_app_name_locale_fallback.py — all pass
  • Existing integration tests unaffected (filesystem pass still runs first, no regression)
  • launch_app name="ThisAppDoesNotExist_xyzzy" correctly returns isError: true

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved app launching to handle bundle identifiers and case-insensitive app names as input.
  • Tests

    • Added integration tests for app name and bundle identifier resolution scenarios.

Review Change Stack

AppLauncher.locate() now tries three passes when only `name` is given:

  1. Filesystem lookup by bundle filename (existing — fast, locale-independent
     for English names whose on-disk bundle name matches the display name).

  2. LaunchServices bundle-ID lookup — lets callers pass a bundle identifier
     string as `name` (e.g. "com.apple.calculator") without switching to the
     `bundle_id` parameter.

  3. Fuzzy scan:
     a) Locale-aware localizedName from NSRunningApplication (covers JP/FR/…
        locales where the running app's display name differs from the bundle
        filename, e.g. "計算機" on JP macOS for Calculator).
     b) CFBundleDisplayName / CFBundleName from Info.plist (English; catches
        case-insensitive variants like "calculator" or "CALCULATOR").
     c) Bundle URL stem (filename minus .app) as a final fallback.

All matching is case-insensitive throughout.

Before (old binary):
  launch_app name="com.apple.calculator" → Error: Could not locate app
  launch_app name="calculator"           → Error: Could not locate app

After (new binary):
  launch_app name="com.apple.calculator" → pid=…, bundle_id=com.apple.calculator
  launch_app name="calculator"           → pid=…, bundle_id=com.apple.calculator
  launch_app name="CALCULATOR"           → pid=…, bundle_id=com.apple.calculator
  launch_app name="Calculator"           → pid=…, (unchanged, regression guard)

Adds 5 integration tests covering all new paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented May 12, 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 May 16, 2026 6:53pm

Request Review

@coderabbitai

coderabbitai Bot commented May 12, 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: a9f6c226-12f7-4344-95e6-775e8082432a

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 adds fallback name resolution to AppLauncher so that launch_app accepts bundle identifiers, English app names, and case-insensitive display names as name inputs, addressing silent failures on non-English macOS locales. The implementation introduces a three-pass lookup strategy in Swift and validates the behavior with Python integration tests covering bundle ID resolution, case-insensitive matching, regression coverage, and error handling.

Changes

App Name Fallback Resolution

Layer / File(s) Summary
Multi-pass app name resolution implementation
libs/cua-driver/Sources/CuaDriverCore/Apps/AppLauncher.swift
Pass 1 matches the expected .app bundle filename; Pass 2 attempts NSWorkspace bundle-identifier lookup when name might be a bundle ID; Pass 3 performs case-insensitive matching against running apps' localizedName, then scans directories for installed .app bundles and compares against CFBundleDisplayName, CFBundleName, and bundle filename stem.
Integration tests for name resolution fallback
libs/cua-driver/Tests/integration/test_app_name_locale_fallback.py
Tests verify that launch_app resolves bundle IDs passed as name, matches case-insensitive display names, preserves exact canonical name behavior, and returns errors for unknown app names. Test setup resets the calculator fixture, creates a DriverClient, and cleans up process state on teardown.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit hops through three lookup passes,
Finding apps by bundle, name, and classes,
Bundle IDs and locales now play nice,
Japanese Macs need help no more—precise,
計算機 or "Calculator," both find their way. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 clearly references issue #1481 and concisely summarizes the main changes: extending app name resolution with bundle ID and locale fallbacks.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #1481: multi-pass app name resolution with bundle ID support, case-insensitive matching of display names, and comprehensive integration tests validating the new resolution paths.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #1481: AppLauncher resolution logic and integration tests validate the new name resolution strategy. No extraneous modifications detected.

✏️ 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 cua-driver/fix-1481-upstream

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cua-driver/Sources/CuaDriverCore/Apps/AppLauncher.swift`:
- Around line 293-299: The current logic builds a single displayName by picking
the first non-nil of CFBundleDisplayName, CFBundleName, or the file stem and
then compares it to needle, which skips checking subsequent candidates if the
first exists but doesn't match; update the check in AppLauncher.swift so you
compare needle against each candidate separately (CFBundleDisplayName,
CFBundleName, and the stem from URL(fileURLWithPath:
path).deletingPathExtension().lastPathComponent) — e.g. gather the three
candidate strings and test if any.lowercased() == needle (or otherwise normalize
and compare each) instead of using the single displayName variable for the
match.
🪄 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: 4a79cdd5-bb1a-4c12-b620-1db19da693a9

📥 Commits

Reviewing files that changed from the base of the PR and between 81baebe and d922533.

📒 Files selected for processing (2)
  • libs/cua-driver/Sources/CuaDriverCore/Apps/AppLauncher.swift
  • libs/cua-driver/Tests/integration/test_app_name_locale_fallback.py

Comment on lines +293 to +299
// CFBundleDisplayName > CFBundleName > stem
let displayName =
(bundle.infoDictionary?["CFBundleDisplayName"] as? String)
?? (bundle.infoDictionary?["CFBundleName"] as? String)
?? URL(fileURLWithPath: path)
.deletingPathExtension().lastPathComponent
if displayName.lowercased() == needle {

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 | ⚡ Quick win

CFBundle fallback order is implemented incorrectly

On Line 294-Line 299, CFBundleDisplayName ?? CFBundleName ?? stem only tests one value. If CFBundleDisplayName exists but doesn’t match, CFBundleName is never compared, so valid names can still fail resolution.

Suggested fix
-                    // CFBundleDisplayName > CFBundleName > stem
-                    let displayName =
-                        (bundle.infoDictionary?["CFBundleDisplayName"] as? String)
-                        ?? (bundle.infoDictionary?["CFBundleName"] as? String)
-                        ?? URL(fileURLWithPath: path)
-                            .deletingPathExtension().lastPathComponent
-                    if displayName.lowercased() == needle {
-                        return URL(fileURLWithPath: path)
-                    }
-                    // Also match against the raw stem ("Calculator" → "Calculator.app")
-                    let stem = URL(fileURLWithPath: path)
-                        .deletingPathExtension().lastPathComponent
-                    if stem.lowercased() == needle {
+                    let displayName =
+                        bundle.infoDictionary?["CFBundleDisplayName"] as? String
+                    let bundleName =
+                        bundle.infoDictionary?["CFBundleName"] as? String
+                    let stem = URL(fileURLWithPath: path)
+                        .deletingPathExtension().lastPathComponent
+
+                    if displayName?.lowercased() == needle
+                        || bundleName?.lowercased() == needle
+                        || stem.lowercased() == needle
+                    {
                         return URL(fileURLWithPath: path)
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/Sources/CuaDriverCore/Apps/AppLauncher.swift` around lines
293 - 299, The current logic builds a single displayName by picking the first
non-nil of CFBundleDisplayName, CFBundleName, or the file stem and then compares
it to needle, which skips checking subsequent candidates if the first exists but
doesn't match; update the check in AppLauncher.swift so you compare needle
against each candidate separately (CFBundleDisplayName, CFBundleName, and the
stem from URL(fileURLWithPath: path).deletingPathExtension().lastPathComponent)
— e.g. gather the three candidate strings and test if any.lowercased() == needle
(or otherwise normalize and compare each) instead of using the single
displayName variable for the match.

Regenerated cli-reference.mdx and mcp-tools.mdx via
`npx tsx scripts/docs-generators/cua-driver.ts` to satisfy
`Check Documentation Sync` CI on PR #1492. Drift accumulated
because v0.1.7 was bumped without re-running the generator,
and a few tool docstrings (`get_window_state`, `launch_app`,
`screenshot`) were updated upstream without a docs regen.

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

@f-trycua f-trycua left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving — docs-sync CI is failing on a non-deterministic check that passes locally with the same source and same generator (confirmed via wipe-and-rebuild). Lychee is failing on pre-existing external-link redirects in unrelated files. Substantive code change is sound.

@f-trycua
f-trycua merged commit 3b5c372 into main May 16, 2026
9 of 13 checks passed
@f-trycua
f-trycua deleted the cua-driver/fix-1481-upstream branch May 16, 2026 19:04
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: accept English name / bundle ID as fallback when localized app name doesn't match

2 participants