Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Resetting TCC Permissions Between macOS Test Runs

Recommendation: between each macOS test run, call tccutil reset <Service> <bundleID> for exactly the privacy services your test bundle uses, then verify by observing the next launch — the system permission alert must appear again. Do not read TCC.db directly as proof; the schema, service list and file location are private to Apple and have changed between releases. Scope every reset to one disposable test bundle ID under the current user. On Figment that work runs on the virtual Intel macOS plan at $29/month for user-permission flows, or on an M-series Mac from $95/month when you need ARM execution, Xcode 27 or supervised physical hardware (exact chip, RAM, storage and administrator access confirmed in the quote).

If your test suite depends on a first-run permission dialog — camera, microphone, contacts, calendar, reminders, photos — a leftover grant from an earlier run skips the alert and quietly changes which branch of the test actually executes. The fix is a small, supported pre-step: targeted tccutil reset calls for one bundle ID, followed by a visible fresh-prompt check for each service you care about.

What tccutil reset Actually Does, and What It Doesn't

Apple's Transparency, Consent and Control (TCC) subsystem records user privacy decisions. The one supported command-line surface for clearing those decisions is tccutil. Its local man page documents a single verb — reset — with the form tccutil reset <service> [<bundle-id>]. Read it on your own machine before trusting any blog table of service keys:

man tccutil

What tccutil reset <Service> <bundleID> does reliably on a current, SIP-enabled macOS:

  • Clears the user's prior decision for that one service and one bundle ID.
  • Causes the system to re-prompt the next time that app requests the service.
  • Runs without sudo for user-scope services the current user already owns.

What it does not do, and should not be claimed to do:

  • It does not grant permission. Only the user tapping Allow at the live prompt does that. A Mobile Device Management Privacy Preferences Policy Control (PPPC) profile can grant some services (for example Apple Events, Accessibility, SystemPolicyAllFiles), but Apple's deployment guide is explicit that Camera, Microphone and Screen Recording can be denied by PPPC and cannot be set to Allowed by any MDM — the user still taps Allow in person. See the Apple Platform Deployment entry on the Privacy Preferences Policy Control payload.
  • It is not a universal catalog. The man page notes that the full service list "will vary with the software currently installed." Confirm the exact spelling (Camera, Microphone, ScreenCapture, Accessibility, AddressBook, Calendar, Reminders, Photos, SystemPolicyAllFiles, SpeechRecognition, …) on the macOS release you target.
  • Running as root does not automatically grant anything. Supported reset scope depends on the macOS version and service; verify the resulting permission state in the tested user session.
  • Reading or writing TCC.db directly is unsupported. Treat the SQLite file as an implementation detail that may move, change schema, be sealed by SIP, or require Full Disk Access to even open. "Zero rows in access" is not a reliable success signal — a visible fresh prompt at next launch is.

Skip the broad tccutil reset All and the All + no-bundle forms. They touch more than the suite under test and leave you debugging state you did not intend to change. A targeted reset per service per bundle is the only form this article recommends.

The Targeted Reset Runbook

Save the script below as scripts/tcc-reset.sh. It runs on the macOS default shell (Bash 3.2), targets one bundle ID, resets only the services you list, and leaves a human-readable audit line per call. There is no declare -A (Bash 4-only), no sudo, no identity switching, no TCC.db query.

#!/bin/bash
# tcc-reset.sh — reset TCC services for one test bundle (Bash 3.2 compatible)
# Usage: scripts/tcc-reset.sh com.example.yourtestapp [audit.log]
set -eu

BUNDLE_ID="${1:?Usage: $0 <bundle-id> [audit-log]}"
AUDIT_LOG="${2:-reset-audit.log}"

# Edit this list to the services your test bundle actually uses.
# Confirm names against `man tccutil` on your target macOS version.
SERVICES="Camera Microphone AddressBook Calendar Reminders Photos SpeechRecognition"

: > "$AUDIT_LOG"
printf '=== tcc-reset %s @ %s ===\n' \
  "$BUNDLE_ID" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" | tee -a "$AUDIT_LOG"

for svc in $SERVICES; do
  ts=$(date -u +%H:%M:%SZ)
  if tccutil reset "$svc" "$BUNDLE_ID" >>"$AUDIT_LOG" 2>&1; then
    printf '[%s] %-20s OK\n' "$ts" "$svc" | tee -a "$AUDIT_LOG"
  else
    printf '[%s] %-20s FAIL (unknown service on this macOS?)\n' \
      "$ts" "$svc" | tee -a "$AUDIT_LOG"
    exit 1
  fi
done

printf '=== reset complete; verify each service with a fresh-prompt check ===\n' \
  | tee -a "$AUDIT_LOG"

This is an unexecuted example. Run it once by hand on your macOS target, confirm each service name is accepted on that release, then wire it in. Use a disposable test bundle ID — not the production app identifier — so a failed reset never corrupts a developer's own permission state.

Fresh-prompt verification matrix. After the script runs, the only honest proof the reset worked is that the system prompt appears again at next launch. Keep this matrix next to the suite and tick each row during a dry run:

Service (tccutil arg)Trigger in the test appExpected after reset
CameraAVCaptureDevice.requestAccess(for: .video)System dialog shown
MicrophoneAVCaptureDevice.requestAccess(for: .audio)System dialog shown
AddressBookCNContactStore.requestAccessSystem dialog shown
CalendarEKEventStore.requestFullAccessToEventsSystem dialog shown
RemindersEKEventStore.requestFullAccessToRemindersSystem dialog shown
PhotosPHPhotoLibrary.requestAuthorizationSystem dialog shown
SpeechRecognitionSFSpeechRecognizer.requestAuthorizationSystem dialog shown

If a row stays silent, the reset did not take effect for that service on that macOS version. Re-check the exact service name in man tccutil, confirm the running process really is your test bundle, and quit and relaunch — TCC caches the decision in-process, so a long-running app can keep acting on a stale answer.

Wiring the Reset Into GitHub Actions and Xcode Cloud

GitHub Actions self-hosted macOS runner. Call the script from the job and upload the audit log so each build keeps a record of which services were reset:

- name: Reset TCC for test bundle
  env:
    BUNDLE_ID: com.example.yourtestapp
  run: |
    chmod +x scripts/tcc-reset.sh
    scripts/tcc-reset.sh "$BUNDLE_ID" reset-audit.log

- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: tcc-reset-audit
    path: reset-audit.log

Self-hosted runners do not get the clean-VM guarantee of GitHub-hosted runners; permission state can survive between jobs. GitHub's current hardening guidance for self-hosted runners recommends ephemeral just-in-time runners and tight access control. Pair that posture with the reset step so the suite starts from a known state even on a long-lived runner.

Xcode Cloud. Xcode Cloud reads its custom build scripts from a ci_scripts/ folder at the repository root — not from the repo root itself. Create ci_scripts/ci_pre_xcodebuild.sh, mark it executable, and invoke the reset script from there:

#!/bin/sh
# ci_scripts/ci_pre_xcodebuild.sh
set -eu
"$CI_WORKSPACE/scripts/tcc-reset.sh" "com.example.yourtestapp" \
  "$CI_DERIVED_DATA_PATH/reset-audit.log"

Xcode Cloud runs on Apple-managed hosts; privacy prompts outside the simulator cannot be answered interactively there, so keep tests that require a live Allow tap on a Mac you own or rent, and use simulator-driven paths for the hosted service.

Where Figment Fits for a Clean TCC Host

  • $29/month virtual Intel macOS. A disposable macOS environment where the current user can reset and re-prompt user-scope services (Camera entitlement flow, Microphone entitlement flow, Contacts, Calendar, Reminders, Photos, Speech Recognition) in Xcode's simulator and in compatible Intel Xcode (Xcode 26 or earlier). The VM is Intel on Linux/KVM — no Apple-silicon execution, no GPU acceleration and no audio input device, so tests that need live hardware capture still belong on a physical Mac.
  • M-series Mac from $95/month. Physical Apple silicon for ARM execution, Xcode 27 or tests that need a physical device or GPU. Plans are provisioned on request; the configuration quote confirms the exact chip, RAM, storage, installed macOS and whether an administrator-capable account is attached. Ask explicitly for the account type and required permissions. Privacy controls alone do not require Apple silicon.
  • Managed Build Mac at $249/month. One M-series Mac with CI setup, backups, monitoring and 30 minutes of monthly support.
  • Managed Mac Team at $999/month. Two M-series Macs, two CI runners, team setup, backups, monitoring and one hour of monthly support.

Request a configuration quote at /mac-hosting/start and describe the exact services your suite resets, the macOS version you target, and whether tests need a live permission tap or run only in the simulator.


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms