Skip to content

fix(settings): resolve a display-name --app for app-scoped settings - #3386

Merged
thymikee merged 7 commits into
mainfrom
fix/settings-app-resolution
Oct 11, 2026
Merged

thymikee merged 7 commits into
mainfrom
fix/settings-app-resolution

Conversation

@thymikee

@thymikee thymikee commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Summary

settings permission … --app <name> (and iOS location on|off --app) sent a display name to simctl privacy or pm as a literal id and reported success. App-scoped settings (permission, iOS location on|off, clear-app-state) now resolve the app through each platform's shared resolver, using a new installedOnly mode:

  • iOS simulator: --app Maps resolves to com.apple.Maps, and Booking.com resolves too. A bundle id must be installed.
  • Android: a package must be installed exactly. Any other name is matched against installed package names. An intent alias is refused with setting_app_not_package, and the permission request is validated before resolution.
  • Refusals: an app that resolves to nothing fails with APP_NOT_INSTALLED, reason: app_not_installed, dispatched: no, and a hint pointing to apps --all.
  • Listing failures: a simctl listapps that fails or can't be parsed reports COMMAND_FAILED, not a missing app. This also applies to open by name.
  • open/close: behavior is otherwise unchanged; they keep the pass-through.
  • Limrun iOS: it keeps alias-only resolution (SimctlSettingRequest.resolveApp), and the docs say so.

The help text, client types, and commands.md are updated. Closes #3384

Validation

Commit df68072 (rebased on main; live runs on 4b1aebc, which differs only in the Android name cache):

  • pnpm check:affected --run passed all runnable checks (1433 test files, 12601 tests, fallow clean). Coverage and provider integration run in CI.
  • New tests fail without the fix.
  • Live transcripts are in this comment. They ran on this head:
    • iOS 27.1 simulator:
      • --app Maps wrote the TCC.db row for com.apple.Maps.
      • location on/off --app Maps flipped locationd's Authorization for Maps.
      • clear-app-state --app Maps cleared com.apple.Maps.
      • An unknown name was refused with app_not_installed and dispatched: no.
    • Android API 35 emulator:
      • --app Chrome granted CAMERA to com.android.chrome; I reset it afterwards.
      • An unknown name and an uninstalled package were refused with app_not_installed.
  • findIosSimulatorInstalledApp now delegates to resolveIosApp with installedOnly. It still returns undefined for missing, ambiguous, or unreadable listings. Android lists the acting user's packages (--user <id>).

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-11 09:53 UTC

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.16 MB 5.16 MB +2.8 kB
Package (unpacked) 5.16 MB 5.16 MB +2.8 kB
Package (download) 1.55 MB 1.55 MB +1.2 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.7 ms 26.8 ms +0.1 ms
CLI --help 82.3 ms 85.2 ms +2.9 ms

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 15 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread src/commands/capture/settings.ts Outdated
Comment thread website/docs/docs/commands.md Outdated
Comment thread packages/platform-apple/src/core/app-settings.ts
Comment thread src/commands/capture/settings.ts Outdated
Comment thread packages/contracts/src/client-settings.ts Outdated
Comment thread packages/contracts/src/settings.ts Outdated
Comment thread packages/platform-android/src/settings.ts Outdated
Comment thread packages/platform-android/src/settings.ts
thymikee added a commit that referenced this pull request Oct 10, 2026
Address review on #3386:
- iOS settings resolve the app with resolveIosApp's new installedOnly
  mode: a dotted name is matched against installed bundle ids, then
  display names (Booking.com resolves), and an uninstalled id is refused
  with app_not_installed instead of written to TCC. open/close keep the
  default pass-through.
- app_not_installed carries a settings hint pointing at `apps --all`.
- Android refuses an intent alias with reason setting_app_not_package,
  and validates the permission request before resolving the app.
- Help, client docs and commands.md scope resolution per target,
  including Limrun iOS alias-only resolution.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 12 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread website/docs/docs/commands.md Outdated
Comment thread packages/platform-apple/src/core/app-resolution.ts Outdated
thymikee added a commit that referenced this pull request Oct 10, 2026
…failures distinct

Address review on #3386:
- Android settings resolve with resolveAndroidApp's new installedOnly
  mode: a package-shaped id must be an installed package (exact match,
  no fuzzy fallback), so an uninstalled package is refused with
  app_not_installed before pm runs. open/close keep the pass-through.
- A simctl listapps that fails or answers unparsably is now a
  COMMAND_FAILED enumeration failure in resolveIosApp, not
  APP_NOT_INSTALLED, so an installed app is never reported missing.
- Permission tests target setAndroidPermission, the module they cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 10 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/platform-android/src/app-deployment-resolution.ts
Comment thread packages/contracts/src/client-settings.ts
Comment thread packages/platform-android/src/__tests__/settings-permission.test.ts
Comment thread packages/platform-android/src/app-deployment-resolution.ts Outdated
thymikee added a commit that referenced this pull request Oct 10, 2026
Address review on #3386:
- installedOnly resolution on Android and iOS no longer reuses a cached
  name resolution, so an app uninstalled within the cache TTL is refused
  instead of reaching pm or simctl privacy.
- Document that Android installedOnly may still answer an alias intent
  the caller must refuse; rewrap the client app doc.
- Name the permission tests after setAndroidPermission, which they call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 6 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/platform-apple/src/core/app-resolution.ts Outdated
@thymikee

Copy link
Copy Markdown
Member Author

The code in 9c73056 looks right to me, and all 21 checks pass at that commit. I found nothing that blocks merge. I am holding the approval until the live runs are shown, because I did not re-run them and no transcript is attached. They were reported for f1227d7, and the head differs from that only by a doc comment. Please attach the iOS and Android run output that shows a display-name --app resolving, and a refusal with app_not_installed for a name that is not installed. iOS location on|off --app <name> and clear-app-state by display name were not shown live, so a short run of each would close that gap. I read the new tests and they look like they would catch the old behavior, but I did not run them against the pre-fix code. There are no conflicts.

Not blocking: findIosSimulatorInstalledApp already resolves installed-only on the simulator, and the new installedOnly branch in resolveIosApp is a second path for the same question. A follow-up could make the first delegate to resolveIosApp(..., { installedOnly: true }) and map APP_NOT_INSTALLED to undefined. Also, installedOnly on Android lists packages with pm list packages and no --user, while setAndroidPermission acts on the foreground user. A package installed only for a secondary foreground user might then be refused where it was granted before, so passing --user <id> from requireAndroidPermissionUser would be safer. I did not check that on a device. You can take or leave both.

Is there a smaller design? I looked for one. Resolving appBundleId up front in setIosSetting, as Limrun did, would drop the resolveApp callback on SimctlSettingRequest. It would then resolve before the permission request is parsed, and I see why you ordered it after validation. The one consolidation I would still want is the findIosSimulatorInstalledApp fold above, so there is a single installed-only rule.

thymikee and others added 6 commits October 11, 2026 09:01
`settings permission ... --app Maps` (and iOS `location on|off --app`)
sent the name to `simctl privacy` / `pm grant` as a literal client id and
reported success. App-scoped settings now resolve the named app through
the same platform resolution `clear-app-state` uses: an id passes
through, a display name resolves to the installed app's id, and an app
that resolves to nothing refuses with APP_NOT_INSTALLED,
details.reason app_not_installed, and dispatched: no.

Closes #3384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address review on #3386:
- iOS settings resolve the app with resolveIosApp's new installedOnly
  mode: a dotted name is matched against installed bundle ids, then
  display names (Booking.com resolves), and an uninstalled id is refused
  with app_not_installed instead of written to TCC. open/close keep the
  default pass-through.
- app_not_installed carries a settings hint pointing at `apps --all`.
- Android refuses an intent alias with reason setting_app_not_package,
  and validates the permission request before resolving the app.
- Help, client docs and commands.md scope resolution per target,
  including Limrun iOS alias-only resolution.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…failures distinct

Address review on #3386:
- Android settings resolve with resolveAndroidApp's new installedOnly
  mode: a package-shaped id must be an installed package (exact match,
  no fuzzy fallback), so an uninstalled package is refused with
  app_not_installed before pm runs. open/close keep the pass-through.
- A simctl listapps that fails or answers unparsably is now a
  COMMAND_FAILED enumeration failure in resolveIosApp, not
  APP_NOT_INSTALLED, so an installed app is never reported missing.
- Permission tests target setAndroidPermission, the module they cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address review on #3386:
- installedOnly resolution on Android and iOS no longer reuses a cached
  name resolution, so an app uninstalled within the cache TTL is refused
  instead of reaching pm or simctl privacy.
- Document that Android installedOnly may still answer an alias intent
  the caller must refuse; rewrap the client app doc.
- Name the permission tests after setAndroidPermission, which they call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No settings path reaches resolveIosApp with installedOnly on the macOS
host, which keeps resolving through resolveMacOsApp; say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Android user

Address review on #3386:
- findIosSimulatorInstalledApp delegates to resolveIosApp's installedOnly
  mode. It stays lenient: a missing or ambiguous app and an unreadable
  simctl app list (now typed app-list-unreadable) answer undefined, and
  other failures still propagate. installedOnly also verifies an alias
  (settings -> com.apple.Preferences) is installed, as the probe did.
- Android permission settings read the acting user once and list that
  user's packages (`pm list packages --user <id>`), so a package
  installed only for a secondary foreground user is not refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee
thymikee force-pushed the fix/settings-app-resolution branch from 9c73056 to 4b1aebc Compare October 11, 2026 07:22
@thymikee

Copy link
Copy Markdown
Member Author

Live runs on head 4b1aebc99 (rebased on main, after #3385), using the worktree CLI with a private daemon state dir. iOS ran on the iOS 27.1 simulator iPhone 17 Pro Max. Android ran on the ReactNative_API_35 emulator, which I booted for this run and killed afterwards. Diagnostic ids and local log paths are trimmed from the JSON.

Case Result
a iOS permission grant photos --app Maps ✅ TCC.db kTCCServicePhotos|com.apple.Maps|2
b iOS --app NoSuchAppXyz ✅ APP_NOT_INSTALLED, reason: app_not_installed, dispatched: no, and TCC.db unchanged
c iOS location on/off --app Maps ✅ locationd com.apple.Maps Authorization 1 → 2 → 1
d iOS clear-app-state --app Maps ✅ bundleId: com.apple.Maps, cleared: true
e Android permission grant camera --app Chrome ✅ resolved to com.android.chrome, CAMERA granted=true, then reset to granted=false
f Android unknown name and uninstalled package ✅ APP_NOT_INSTALLED, reason: app_not_installed, dispatched: no

Cleanup: I reset the photos and location grants for Maps, stopped the daemon, shut the simulator down and killed the emulator.

a. iOS display name grant
$ agent-device settings permission grant photos --app Maps --platform ios --udid DBEA214F-555F-4DDA-B62B-E34285CE63DB --json
{
  "success": true,
  "data": {
    "setting": "permission",
    "state": "grant",
    "message": "Updated setting: permission"
  }
}
exit=0
$ sqlite3 TCC.db "select service, client, auth_value from access where service='kTCCServicePhotos'"
kTCCServicePhotos|com.apple.Maps|2
b. iOS unknown name refused
$ agent-device settings permission grant photos --app NoSuchAppXyz --platform ios --udid DBEA214F-555F-4DDA-B62B-E34285CE63DB --json
{
  "success": false,
  "error": {
    "code": "APP_NOT_INSTALLED",
    "message": "No app found matching \"NoSuchAppXyz\"",
    "hint": "Run apps --all for the exact bundle id or package name, or install the app, then retry with that id in --app.",
    "details": {
      "reason": "app_not_installed",
      "dispatched": "no",
      "app": "NoSuchAppXyz"
    }
  }
}
exit=1
$ sqlite3 TCC.db "select count(*) from access where client in ('NoSuchAppXyz','Maps')"
0
c. iOS location on/off by display name
$ agent-device settings location off --app Maps --platform ios --udid DBEA214F-555F-4DDA-B62B-E34285CE63DB --json
{
  "success": true,
  "data": {
    "setting": "location",
    "state": "off",
    "message": "Updated setting: location"
  }
}
exit=0
$ plutil -p locationd/clients.plist  # icom.apple.Maps: entry, Authorization/BundleId
  "icom.apple.Maps:" => {
    "Authorization" => 1
    "BundleId" => "com.apple.Maps"
$ agent-device settings location on --app Maps --platform ios --udid DBEA214F-555F-4DDA-B62B-E34285CE63DB --json
{
  "success": true,
  "data": {
    "setting": "location",
    "state": "on",
    "message": "Updated setting: location"
  }
}
exit=0
$ plutil -p locationd/clients.plist  # icom.apple.Maps: entry, Authorization/BundleId
  "icom.apple.Maps:" => {
    "Authorization" => 2
    "BundleId" => "com.apple.Maps"
$ agent-device settings location off --app Maps --platform ios --udid DBEA214F-555F-4DDA-B62B-E34285CE63DB --json
{
  "success": true,
  "data": {
    "setting": "location",
    "state": "off",
    "message": "Updated setting: location"
  }
}
exit=0
$ plutil -p locationd/clients.plist  # icom.apple.Maps: entry, Authorization/BundleId
  "icom.apple.Maps:" => {
    "Authorization" => 1
    "BundleId" => "com.apple.Maps"
d. iOS clear-app-state by display name
$ agent-device settings clear-app-state --app Maps --platform ios --udid DBEA214F-555F-4DDA-B62B-E34285CE63DB --json
{
  "success": true,
  "data": {
    "setting": "clear-app-state",
    "state": "clear",
    "bundleId": "com.apple.Maps",
    "containerPath": "<sim>/Containers/Data/Application/4C2D1C1D-7A43-4737-B822-299D9FDB7815",
    "cleared": true,
    "message": "Cleared user data for Maps"
  }
}
exit=0
e. Android display name grant, then reset
$ adb -s emulator-5554 shell dumpsys package com.android.chrome | grep 'permission.CAMERA: granted'
        android.permission.CAMERA: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED]
$ agent-device settings permission grant camera --app Chrome --platform android --serial emulator-5554 --json
{
  "success": true,
  "data": {
    "setting": "permission",
    "state": "grant",
    "permission": "camera",
    "permissions": [
      "android.permission.CAMERA"
    ],
    "message": "Updated setting: permission"
  }
}
exit=0
$ adb -s emulator-5554 shell dumpsys package com.android.chrome | grep 'permission.CAMERA: granted'
        android.permission.CAMERA: granted=true, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED]
$ agent-device settings permission reset camera --app com.android.chrome --platform android --serial emulator-5554 --json
{
  "success": true,
  "data": {
    "setting": "permission",
    "state": "reset",
    "permission": "camera",
    "permissions": [
      "android.permission.CAMERA"
    ],
    "priorGrantState": "granted",
    "warnings": [
      "android.permission.CAMERA was granted before this revoke, and Android kills an app when a granted permission is revoked: if com.android.chrome was running it is no longer. Relaunch it with open com.android.chrome --relaunch before the next interaction."
    ],
    "message": "Updated setting: permission"
  }
}
exit=0
$ adb -s emulator-5554 shell dumpsys package com.android.chrome | grep 'permission.CAMERA: granted'
        android.permission.CAMERA: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED]
f. Android unknown name and uninstalled package refused
$ agent-device settings permission grant camera --app NoSuchAppXyz --platform android --serial emulator-5554 --json
{
  "success": false,
  "error": {
    "code": "APP_NOT_INSTALLED",
    "message": "No package found matching \"NoSuchAppXyz\"",
    "hint": "Run apps --all for the exact bundle id or package name, or install the app, then retry with that id in --app.",
    "details": {
      "reason": "app_not_installed",
      "dispatched": "no",
      "app": "NoSuchAppXyz"
    }
  }
}
exit=1
$ agent-device settings permission grant camera --app com.example.notinstalled --platform android --serial emulator-5554 --json
{
  "success": false,
  "error": {
    "code": "APP_NOT_INSTALLED",
    "message": "No package found matching \"com.example.notinstalled\"",
    "hint": "Run apps --all for the exact bundle id or package name, or install the app, then retry with that id in --app.",
    "details": {
      "reason": "app_not_installed",
      "dispatched": "no",
      "app": "com.example.notinstalled"
    }
  }
}
exit=1

Your two non-blocking points (both taken in 4b1aebc99):

  1. Single installed-only rule. findIosSimulatorInstalledApp now delegates to resolveIosApp(device, app, { installedOnly: true }). It keeps its lenient contract for device selection: a missing app (APP_NOT_INSTALLED), an ambiguous name, and an app list simctl could not produce all return undefined. The unreadable listing now carries a typed details.reason: app-list-unreadable, so the mapping doesn't depend on the error code alone. Anything else still propagates, including a listing timeout, as before. To match the old probe, installedOnly now also checks that an alias (settings → com.apple.Preferences) is installed. One change for device selection: a dotted display name such as Booking.com now resolves to its bundle id instead of returning undefined. The matching logic is factored into matchInstalledIosApp, which also keeps resolveIosApp under the fallow complexity limit.
  2. Android --user. settings permission now reads the acting user once with requireAndroidPermissionUser. It lists that user's packages with pm list packages --user <id> and passes the same id to setAndroidPermission, so a package installed only for a secondary foreground user is accepted. A new unit test covers user 10. clear-app-state keeps the device default, which is also what pm clear uses.

Gate: pnpm check:affected --run passed on 4b1aebc99 (1433 test files, 12600 tests, fallow clean). I updated the android-world provider fake to answer pm list packages --user 0.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 8 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/platform-android/src/app-deployment-resolution.ts
Comment thread packages/platform-apple/src/core/__tests__/app-resolution.test.ts Outdated
Comment thread packages/platform-apple/src/core/app-resolution.ts Outdated
Address review on #3386: a `pm list packages --user <id>` resolution
was written into the device-wide name cache, so a later default-user
lookup could reuse a package absent for that user. Only device-default
listings are cached now. Also retitle the alias test and move the
app-list-unreadable reason above the requireSimulatorApps doc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member Author

Head is now df6807242, which only changes the Android name cache: a user-scoped pm list packages --user <id> lookup is no longer cached. Settings resolution never read that cache (installedOnly lists fresh every time), so the live-checked behavior in (e) and (f) above is unchanged.

@thymikee

Copy link
Copy Markdown
Member Author

I reviewed df68072 and found no code problems. The Android name-cache key now includes the user, and the new regression test in app-deployment.test.ts covers that case. The earlier ask from the 4b1aebc round is done.

All three earlier inline threads from the other review are fixed at this commit: the user-keyed cache in app-deployment-resolution.ts (#3386 (comment)), the renamed test title in app-resolution.test.ts (#3386 (comment)), and the moved doc comment in app-resolution.ts (#3386 (comment)). Please resolve these three threads.

Required evidence is in place. The live transcripts are yours from the run at 4b1aebc. I did not re-run them, and df68072 does not touch the route they exercise. That run used user 0 on ReactNative_API_35, so the secondary-user route (--user 10) is covered only by the fake-adb unit test. I did not run the unit tests either; I checked that the new regression fails on the pre-df68072 code by reading it.

CI is pending. Smoke Tests is still queued, with no failure so far. Those lanes can reach the Android and iOS app-resolution route this PR changes, so a result there would count for this PR. There are no conflicts. Once Smoke Tests passes, this is ready for a maintainer review.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 11, 2026
@thymikee
thymikee merged commit 0692458 into main Oct 11, 2026
21 of 22 checks passed
@thymikee
thymikee deleted the fix/settings-app-resolution branch October 11, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

settings permission --app writes a display name as the bundle id instead of resolving it

1 participant