Skip to content

macOS: Child processes cannot access TCC-protected resources (Photos, Speech Recognition, Location, Contacts, etc.) #307364

Description

macOS: Child processes spawned from VS Code terminal/debug cannot access TCC-protected resources (Photos, Speech Recognition, Location, Contacts, etc.)

Problem

On macOS, processes spawned from VS Code's integrated terminal or debug adapter (e.g., flutter run -d macos, Electron apps, Swift CLI tools) cannot request or receive TCC permissions for most privacy-protected resources. The macOS permission dialog either never appears, or access is silently denied.

This affects all TCC-protected resources except Camera, Microphone, and Apple Events (which were added in #119787 / #95062).

Root Cause: macOS "Responsible Process" Model

macOS TCC (Transparency, Consent, and Control) uses a "responsible process" model. When VS Code spawns a child process via child_process.spawn(), node-pty, or the integrated terminal, macOS traces up the process tree and identifies VS Code as the responsible process for all TCC permission requests from that child.

When a child process requests access to a protected resource (e.g., Photo Library), macOS TCC checks:

  1. Does the responsible process (VS Code) have the corresponding NS*UsageDescription key in its Info.plist?
  2. If yes → show permission dialog attributed to VS Code
  3. If no → silently deny the request (no dialog, no error — just empty/failure results)

VS Code's Info.plist currently includes usage descriptions for:

  • ✅ NSMicrophoneUsageDescription
  • ✅ NSCameraUsageDescription
  • ✅ NSAppleEventsUsageDescription
  • ✅ NSAudioCaptureUsageDescription
  • ✅ NSBluetoothAlwaysUsageDescription (from Electron defaults)

But is missing descriptions for:

  • ❌ NSPhotoLibraryUsageDescription — Photo Library
  • ❌ NSSpeechRecognitionUsageDescription — Speech Recognition
  • ❌ NSContactsUsageDescription — Contacts
  • ❌ NSCalendarsUsageDescription — Calendars
  • ❌ NSRemindersUsageDescription — Reminders
  • ❌ NSLocationUsageDescription — Location Services
  • ❌ NSLocalNetworkUsageDescription — Local Network discovery
  • ❌ NSAppleMusicUsageDescription — Media Library / Apple Music

Impact

Any developer building macOS apps that access these resources cannot develop or debug from VS Code. The app works when launched standalone or from Xcode, but fails when launched from VS Code's terminal or debug adapter. This is particularly confusing because:

  • No error message explains why — TCC silently denies access
  • The developer's own app has correct entitlements and Info.plist keys
  • The same code works when launched outside VS Code
  • On macOS 15 (Tahoe) and later, the silent denial behavior is stricter

Common scenarios affected:

  • Flutter desktop apps accessing Photo Library, Contacts, Location
  • Electron apps using Speech Recognition, Media Library
  • Swift/SwiftUI CLI tools or apps accessing any of these resources
  • React Native macOS apps using native privacy-protected APIs
  • Any npm run / cargo run / etc. that spawns a process needing TCC permissions

Reproduction

  1. Create a macOS app that requests Photo Library access (e.g., Flutter app using photo_manager or image_picker package)
  2. Ensure the app has correct NSPhotoLibraryUsageDescription in its own Info.plist and correct entitlements
  3. Launch the app from VS Code's integrated terminal via flutter run -d macos or equivalent
  4. Trigger the photo access feature
  5. Expected: macOS shows "Visual Studio Code wants to access the Photo Library" dialog
  6. Actual: No dialog appears, photo access silently fails or returns empty results

Compare: Launch the same app directly via open build/macos/Build/Products/Debug/MyApp.app — the permission dialog appears correctly.

Proposed Fix

Add the missing NS*UsageDescription keys to VS Code's Info.plist during the build signing step in build/darwin/sign.ts, following the exact same pattern already used for Camera, Microphone, and Apple Events:

// These already exist:
await spawn('plutil', ['-insert', 'NSAppleEventsUsageDescription', '-string',
    'An application in Visual Studio Code wants to use AppleScript.', infoPlistPath]);
await spawn('plutil', ['-replace', 'NSMicrophoneUsageDescription', '-string',
    'An application in Visual Studio Code wants to use the Microphone.', infoPlistPath]);

// Proposed additions (same pattern):
await spawn('plutil', ['-insert', 'NSPhotoLibraryUsageDescription', '-string',
    'An application in Visual Studio Code wants to access the Photo Library.', infoPlistPath]);
await spawn('plutil', ['-insert', 'NSSpeechRecognitionUsageDescription', '-string',
    'An application in Visual Studio Code wants to use Speech Recognition.', infoPlistPath]);
await spawn('plutil', ['-insert', 'NSContactsUsageDescription', '-string',
    'An application in Visual Studio Code wants to access Contacts.', infoPlistPath]);
await spawn('plutil', ['-insert', 'NSCalendarsUsageDescription', '-string',
    'An application in Visual Studio Code wants to access Calendars.', infoPlistPath]);
await spawn('plutil', ['-insert', 'NSRemindersUsageDescription', '-string',
    'An application in Visual Studio Code wants to access Reminders.', infoPlistPath]);
await spawn('plutil', ['-insert', 'NSLocationUsageDescription', '-string',
    'An application in Visual Studio Code wants to access your Location.', infoPlistPath]);
await spawn('plutil', ['-insert', 'NSLocalNetworkUsageDescription', '-string',
    'An application in Visual Studio Code wants to access the Local Network.', infoPlistPath]);
await spawn('plutil', ['-insert', 'NSAppleMusicUsageDescription', '-string',
    'An application in Visual Studio Code wants to access the Media Library.', infoPlistPath]);

No entitlement changes in app-entitlements.plist are needed — VS Code is not sandboxed (com.apple.security.app-sandbox is absent), so the Info.plist usage descriptions alone are sufficient for TCC to show permission dialogs.

Security Consideration

This was discussed in #119787. Adding usage descriptions does not grant VS Code automatic access — it only allows macOS to show the permission dialog when a child process requests access. The user still has to explicitly approve each permission, and can revoke it at any time in System Settings → Privacy & Security.

The same trade-off accepted for Camera, Microphone, and Apple Events applies here: any extension or terminal process could trigger the dialog, but the user always has the final say. This is the same model used by Terminal.app, iTerm2, and Xcode.

Long-Term: responsibility_spawnattrs_setdisclaim()

A more granular long-term solution would be to use the responsibility_spawnattrs_setdisclaim() mechanism (as adopted by Qt Creator) to make child processes their own responsible process for TCC purposes. This would let child apps show permission dialogs attributed to themselves rather than to VS Code. However, this requires native code changes across node-pty, potentially Electron's utilityProcess, and the debug adapter spawn path — a much larger effort.

Environment

  • VS Code: 1.114.0
  • macOS: 26.4 (25E246) — but the issue exists on all macOS versions with TCC
  • Affected file: build/darwin/sign.ts (lines 98–127)

Related Issues

Activity

  1. added this to the Backlog milestone on Apr 2, 2026
  2. deepak1556 commented on Apr 2, 2026

    @deepak1556
    Collaborator

    I agree with the issue but we cannot keep adding all the usage descriptions to VSCode application. We can start with disclaiming tcc for the pty host process which is already possible.

  3. CVirus commented on May 12, 2026

    @CVirus

    Confirmed on macOS 26.4.1 — tccd actively suppresses the prompt with Policy disallows prompt for Sub:{com.microsoft.VSCode}.... The same ical binary works fine from Terminal/iTerm/Zed/kitty on the same machine, isolating it to VSCode's missing entitlements.

  4. 111fanyj commented on Jun 16, 2026

    @111fanyj

    Or is there a way for us to show some kind of permission-related error or prompt? After testing for a while, we found that both the system terminal and third-party terminals work fine. We checked the app’s Info.plist and confirmed the root cause. This is really annoying.

  5. pgolawsk commented on Sep 6, 2026

    @pgolawsk

    +1 — running into this with EventKit-based CLI tools, not just debugged apps.

    Use case: shelling out to tools like keith/reminders-cli (and Calendar/Contacts equivalents) from the integrated terminal — increasingly via coding agents that run commands for you (Claude Code, Codex, etc.). From Terminal.app or iTerm2 the tool triggers a TCC prompt once and then works. From the VS Code integrated terminal it fails instantly with error: you need to grant reminders access and no prompt ever appears, because com.microsoft.VSCode ships no NSRemindersUsageDescription.

    The user cannot work around it:

    • macOS 26 removed the manual + "add application" button for the Reminders / Calendar / Contacts panes, so VS Code can't be added by hand.
    • tccutil reset Reminders com.microsoft.VSCode doesn't help — there's no usage string, so nothing to prompt for.

    Same root cause is being reported all over downstream: keith/reminders-cli#68 & #58, openai/codex#21228, anthropics/claude-code#63032.

    Implementation note: on macOS 14+ EventKit split into granular scopes, so the current key names are NSRemindersFullAccessUsageDescription and NSCalendarsFullAccessUsageDescription (the legacy NSRemindersUsageDescription / NSCalendarsUsageDescription still work as fallback). Adding these in build/darwin/sign.ts alongside the existing Camera/Mic/AppleEvents entries (per #119787) would resolve the whole class.

    Complementary approach worth considering: call responsibility_spawnattrs_setdisclaim() when spawning integrated-terminal / debug child processes, so each child becomes its own responsible process and shows a prompt attributed to itself rather than to VS Code. Qt Creator went this route (writeup); standalone implementation in torarnv/disclaim. It only helps child binaries that carry their own Info.plist, so it's additive to the usage-string fix, not a replacement — but it also stops VS Code's name from showing on prompts for things a child tool requested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

feature-requestRequest for new features or functionality

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions