Skip to content

feat: thread app_bundle_id through open_app tool#7434

Merged
Jasonnnz merged 1 commit into
feature/qa-video-automationfrom
swarm/qa-rel-3-thread-app-bundle-id
Feb 24, 2026
Merged

feat: thread app_bundle_id through open_app tool#7434
Jasonnnz merged 1 commit into
feature/qa-video-automationfrom
swarm/qa-rel-3-thread-app-bundle-id

Conversation

@Jasonnnz
Copy link
Copy Markdown
Contributor

@Jasonnnz Jasonnnz commented Feb 24, 2026

Summary

  • Add app_bundle_id as an optional string parameter to the computer_use_open_app tool definition (both definitions.ts and TOOLS.json)
  • In computer-use-session.ts, inject the session's targetAppBundleId as the default app_bundle_id when the LLM doesn't provide one
  • Add appBundleId: String? field to the Swift AgentAction struct and wire it through the initializer
  • Extract app_bundle_id / appBundleId from the IPC input dictionary in Session.swift's mapToAgentAction()
  • Pass action.appBundleId to the existing openApp(name:bundleId:) method instead of nil

This threads the bundleId from target-app-hints through the tool definition, IPC, and Swift action types so that openApp can use precise bundle identifier resolution instead of just fuzzy name matching.

Test plan

  • Verify TypeScript type-check passes (pre-existing errors only)
  • Verify a CU session with a target app hint correctly populates app_bundle_id in the IPC action message
  • Verify Swift side receives and passes bundleId to openApp(), which activates via bundle ID when available
  • Verify fallback: when no app_bundle_id is provided, behavior is identical to before (name-based resolution)

🤖 Generated with Claude Code


Open with Devin

The target-app-hints system resolves app names to bundleId pairs, and
the Swift openApp() already accepts bundleId, but the value was lost
between the daemon and Swift because it was never included in the tool
schema or IPC payload.

Changes:
- Add app_bundle_id as optional param to computer_use_open_app tool
  definition (definitions.ts + TOOLS.json)
- Inject session's targetAppBundleId as default when LLM omits it
  (computer-use-session.ts)
- Add appBundleId field to Swift AgentAction struct (ActionTypes.swift)
- Extract app_bundle_id from IPC input dict (Session.swift)
- Pass action.appBundleId to openApp() call (ActionExecutor.swift)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Jasonnnz Jasonnnz merged commit 21ced14 into feature/qa-video-automation Feb 24, 2026
@Jasonnnz Jasonnnz deleted the swarm/qa-rel-3-thread-app-bundle-id branch February 24, 2026 01:47
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 389ee232a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +689 to +690
if (!input.app_bundle_id && this.targetAppBundleId) {
input = { ...input, app_bundle_id: this.targetAppBundleId };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Skip target bundle injection for allowed cross-app steps

This defaulting logic runs whenever app_bundle_id is missing, even when taskExplicitlyRequestsCrossApp() has already allowed switching to a different app_name. In cross-app tasks, that means a step like “open Slack” can be rewritten with the target app’s bundle ID, and because openApp resolves bundle IDs before names, the executor activates the wrong app and the workflow gets stuck. Restrict this injection to cases where the requested app is still the target app.

Useful? React with 👍 / 👎.

case .openApp:
guard let appName = action.appName else { throw ExecutorError.appNotFound("(no name)") }
try await openApp(name: appName)
try await openApp(name: appName, bundleId: action.appBundleId)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce target-app guard on bundle ID activations

Passing action.appBundleId directly into openApp introduces a scope bypass: the daemon’s fail-closed check only validates app_name, but bundle ID is the first resolver in openApp. A model step can therefore use a target-matching app_name plus a different app_bundle_id to activate another app in sessions that are supposed to be single-app constrained.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +688 to +691
// Inject targetAppBundleId when the LLM didn't provide one
if (!input.app_bundle_id && this.targetAppBundleId) {
input = { ...input, app_bundle_id: this.targetAppBundleId };
}
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.

🔴 Target app bundle ID injected for non-target app in cross-app workflows

When a user's task explicitly requests a cross-app workflow (e.g. "Copy from Chrome and paste into Safari"), the guard at lines 670-686 is bypassed because taskExplicitlyRequestsCrossApp() returns true. However, the bundle ID injection at lines 689-690 still unconditionally injects this.targetAppBundleId into the input when the LLM didn't provide one. This causes the target app's bundle ID to be attached to a request to open a different app.

Root Cause and Impact

Consider this scenario:

  • Target app is Safari (com.apple.Safari)
  • User task: "Copy text from Chrome and paste into Safari"
  • LLM calls computer_use_open_app with app_name: "Google Chrome" (no app_bundle_id)
  • The guard is bypassed because taskExplicitlyRequestsCrossApp() is true
  • Line 689: !input.app_bundle_id is true, this.targetAppBundleId is "com.apple.Safari"
  • Line 690: injects app_bundle_id: "com.apple.Safari" into the input
  • On the Swift side at ActionExecutor.swift:350-371, openApp(name: "Google Chrome", bundleId: "com.apple.Safari") is called — bundle ID resolution takes priority, so Safari is activated instead of Chrome.

The fix should only inject the bundle ID when the requested app actually matches the target app (i.e., this.isTargetAppMatch(requestedApp) is true).

Suggested change
// Inject targetAppBundleId when the LLM didn't provide one
if (!input.app_bundle_id && this.targetAppBundleId) {
input = { ...input, app_bundle_id: this.targetAppBundleId };
}
// Inject targetAppBundleId only when the requested app matches the target app
if (!input.app_bundle_id && this.targetAppBundleId && (!requestedApp || this.isTargetAppMatch(requestedApp))) {
input = { ...input, app_bundle_id: this.targetAppBundleId };
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Jasonnnz pushed a commit that referenced this pull request Feb 24, 2026
In cross-app workflows (e.g., "copy from Chrome and paste into Safari"),
the targetAppBundleId was unconditionally injected when the LLM didn't
provide one. This caused the target app's bundle ID to be attached to
requests for a different app, leading to wrong app activation since
bundle ID takes priority in openApp resolution.

Now only injects targetAppBundleId when the requested app matches the
target app (or no app name is specified).

Addresses review feedback from #7434.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Jasonnnz added a commit that referenced this pull request Feb 24, 2026
In cross-app workflows (e.g., "copy from Chrome and paste into Safari"),
the targetAppBundleId was unconditionally injected when the LLM didn't
provide one. This caused the target app's bundle ID to be attached to
requests for a different app, leading to wrong app activation since
bundle ID takes priority in openApp resolution.

Now only injects targetAppBundleId when the requested app matches the
target app (or no app name is specified).

Addresses review feedback from #7434.

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@Jasonnnz
Copy link
Copy Markdown
Contributor Author

Addressed in #7461

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