Skip to content

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

Open
ddupont808 wants to merge 1 commit into
mainfrom
cua-driver/fix-1481-upstream
Open

cua-driver: fix #1481 app name resolution — bundle ID + locale fallbacks#1492
ddupont808 wants to merge 1 commit into
mainfrom
cua-driver/fix-1481-upstream

Conversation

@ddupont808
Copy link
Copy Markdown
Collaborator

@ddupont808 ddupont808 commented May 12, 2026

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
Copy link
Copy Markdown
Contributor

vercel Bot commented May 12, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored May 12, 2026 7:09pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

📝 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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cua-driver/fix-1481-upstream

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.

Copy link
Copy Markdown
Contributor

@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 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.

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

1 participant