cua-driver: fix #1481 app name resolution — bundle ID + locale fallbacks - #1492
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds fallback name resolution to ChangesApp Name Fallback Resolution
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
libs/cua-driver/Sources/CuaDriverCore/Apps/AppLauncher.swiftlibs/cua-driver/Tests/integration/test_app_name_locale_fallback.py
| // CFBundleDisplayName > CFBundleName > stem | ||
| let displayName = | ||
| (bundle.infoDictionary?["CFBundleDisplayName"] as? String) | ||
| ?? (bundle.infoDictionary?["CFBundleName"] as? String) | ||
| ?? URL(fileURLWithPath: path) | ||
| .deletingPathExtension().lastPathComponent | ||
| if displayName.lowercased() == needle { |
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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.
Summary
Fixes #1481 —
launch_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():<name>.appin standard directoriesNSWorkspace.urlForApplication(withBundleIdentifier: name)for callers who pass"com.apple.calculator"asnameNSRunningApplication.localizedName(locale-aware — matches"計算機"on JP macOS)CFBundleDisplayName/CFBundleNamefrom Info.plist (English; case-insensitive).app)Before / After
Issue #1491 (mcp TCC)
Already fixed in PR #1479 (
41c6afdf).cua-driver mcp --helpconfirms--no-daemon-relaunchand the auto-daemon-launch are present on current main. No code changes needed.Test plan
test_app_name_locale_fallback.py— all passlaunch_app name="ThisAppDoesNotExist_xyzzy"correctly returnsisError: true🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests