본문으로 건너뛰기
cccskills
무료GitHub에 공개

macos-tcc-sigkill-missing-usage-description

Fix macOS app hard crashes (EXC_CRASH / SIGKILL) caused by accessing TCC-protected APIs without the matching Info.plist usage description key. Use when: (1) App crashes immediately when touching Photos, Contacts, Calendar, Reminders, Location, Microphone, Camera, Screen Recording, etc., (2) Crash report shows `Exception: EXC_CRASH (SIGKILL)` with `Termination namespace: TCC`, (3) Crash stack includes `TCC.__TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION__`, (4) Termination details contain "This app has crashed because it attempted to access privacy-sensitive data without a usage description", (5) Flutter macOS publish / gallery-save flow crashes on Gal.putVideo, PHPhotoLibrary, CNContactStore, etc. Critical distinction: macOS does NOT return a deny error — it SIGKILLs the process. This skill also covers the NSPhotoLibraryUsageDescription vs NSPhotoLibraryAddUsageDescription distinction that trips many apps.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md8.8 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

macOS TCC SIGKILL: Missing Info.plist Usage Description

Problem

A macOS app crashes with EXC_CRASH (SIGKILL) the instant it calls a privacy-protected API. There is no chance to handle the error, no permission prompt, no deny response — just immediate process termination by the OS.

The crash is caused by macOS TCC enforcement: if your app links against or calls an API that accesses a privacy-protected resource and your Info.plist does NOT contain the matching usage-description key, the OS kills the process for privacy violation.

Context / Trigger Conditions

Crash report (~/Library/Logs/DiagnosticReports/<App>-*.ips) has all of these:

  • "exception": { "type": "EXC_CRASH", "signal": "SIGKILL" }
  • "termination": { "namespace": "TCC", ... }
  • Termination details string contains: "This app has crashed because it attempted to access privacy-sensitive data without a usage description"
  • The crashed thread stack includes TCC.__TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION__
  • Frames below that are usually TCC.__TCCAccessRequest_block_invoke, libxpc.dylib._xpc_connection_reply_callout, dispatch queue machinery

The termination details string tells you exactly which key is missing — read it.

The Info.plist Key Map

ResourceInfo.plist keyEntitlement (sandboxed apps)
CameraNSCameraUsageDescriptioncom.apple.security.device.camera
MicrophoneNSMicrophoneUsageDescriptioncom.apple.security.device.audio-input
Photos (read/write)NSPhotoLibraryUsageDescriptioncom.apple.security.personal-information.photos-library
Photos (add-only)NSPhotoLibraryAddUsageDescriptioncom.apple.security.photos.library.add-only
ContactsNSContactsUsageDescriptioncom.apple.security.personal-information.addressbook
CalendarNSCalendarsUsageDescriptioncom.apple.security.personal-information.calendars
RemindersNSRemindersUsageDescriptioncom.apple.security.personal-information.reminders
LocationNSLocationUsageDescription (+ WhenInUse / Always variants)com.apple.security.personal-information.location
Speech recognitionNSSpeechRecognitionUsageDescription—
Desktop folderNSDesktopFolderUsageDescriptioncom.apple.security.files.user-selected.read-only etc.
Documents folderNSDocumentsFolderUsageDescription—
Downloads folderNSDownloadsFolderUsageDescriptioncom.apple.security.files.downloads.read-write

The Photos trap (critical)

NSPhotoLibraryAddUsageDescription is not a substitute for NSPhotoLibraryUsageDescription. They cover different permission scopes:

  • Add-only (NSPhotoLibraryAddUsageDescription + com.apple.security.photos.library.add-only): sufficient for APIs that only save an asset, like PHAssetCreationRequest from a file, or Gal.putVideo(path) with no album.
  • Full access (NSPhotoLibraryUsageDescription): required for any read, any query, any album lookup/creation, and notably for Gal.putVideo(path, album: 'SomeAlbum') — because "put into album X" has to read or create the album, which is a library-level operation.

Symptom of getting this wrong: save-without-album works, save-with-album crashes.

Solution

  1. Read the crash report's termination details — it names the exact key.
  2. Add the key to your app's Info.plist:
    • Flutter macOS: mobile/macos/Runner/Info.plist
    • Native macOS: <target>/Info.plist or the target's INFOPLIST_FILE build setting
  3. If the app is sandboxed (com.apple.security.app-sandbox = true in release), also add the matching entitlement to both Runner/DebugProfile.entitlements and Runner/Release.entitlements.
  4. Rebuild. Info.plist changes do not hot-reload in Flutter — a full flutter build macos --debug is required.
  5. Relaunch the app.

Example patch

Flutter macOS app crashing on gal.putVideo(path, album: 'MyAlbum'):

<!-- mobile/macos/Runner/Info.plist -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>MyApp needs access to save videos to your Photos library.</string>
<!-- ADD THIS: -->
<key>NSPhotoLibraryUsageDescription</key>
<string>MyApp needs access to your Photos library to organize videos into albums.</string>

Then:

cd mobile
flutter build macos --debug
open build/macos/Build/Products/Debug/MyApp.app

Verification

  1. No crash on the action that previously crashed.
  2. macOS shows a permission prompt the first time (if status is .notDetermined).
  3. Check the new crash reports directory — no new .ips files should appear after the action:
    ls -t ~/Library/Logs/DiagnosticReports/<AppName>-*.ips 2>/dev/null | head -3
    
  4. Verify the built .app bundle contains the key (Info.plist is copied in during build, but sanity-check):
    grep -A1 NSPhotoLibrary build/macos/Build/Products/Debug/MyApp.app/Contents/Info.plist
    
  5. log show --last 2m --predicate 'subsystem == "com.apple.TCC"' should show an authorization grant for the app's bundle ID instead of a crash.

Diagnosing from the crash report

A fast one-liner to extract the key info from a .ips crash report:

python3 -c "
import json, sys
with open(sys.argv[1]) as f:
    f.readline()  # skip header JSON line
    d = json.loads(f.read())
print('Signal:', d.get('exception', {}).get('signal'))
print('Namespace:', d.get('termination', {}).get('namespace'))
print('Details:', d.get('termination', {}).get('details'))
" ~/Library/Logs/DiagnosticReports/MyApp-*.ips

If namespace == 'TCC' and details mention "usage description", this skill applies.

Notes

  • Why SIGKILL and not a graceful deny? Apple decided privacy violations are a policy failure of the developer, not a runtime condition to handle. The OS terminates the process so there is no way for an app to "try anyway" or log workarounds. There is no API to catch this.
  • It applies even to code paths you do not call. If a linked framework or plugin touches a protected resource on your behalf (e.g. a media plugin that probes Photos on init), you need the usage description even if your own code never touches Photos. This is why the key should be added whenever you include a plugin that might access the resource.
  • Hot reload will not rescue you. Flutter hot reload and hot restart do not update Info.plist in the running bundle. You must fully rebuild.
  • iOS vs macOS: The same keys exist on iOS, but on iOS a missing key usually results in a silent deny rather than SIGKILL on older iOS versions. Recent iOS versions align with macOS and will crash. Either way, always set the key.
  • The "Add" vs full Photos distinction was tightened in macOS 14/15/26. Earlier macOS versions let you do some album operations with add-only; recent releases do not. If an app that worked on older macOS starts crashing after a macOS upgrade, check for this first.
  • Related but different: flutter-macos-tcc-responsible-process-camera-denial covers TCC denying camera access when the responsible process attribution is wrong (no crash, just silent denial). That is a different TCC failure mode than the SIGKILL covered here.

References

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Fix ArgoCD ExternalSecret deployment failing with "namespace X is not permitted in project Y". Use when: (1) ExternalSecret shows OutOfSync in ArgoCD but won't sync, (2) ArgoCD application status shows "namespace X is not permitted in project 'infrastructure'", (3) ExternalSecret targets a namespace managed by a different ArgoCD project, (4) Using apps-of-apps pattern with separate infrastructure and application projects.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2662026년 10월 19일 업데이트

Art direction for any content — reads text, PDF, Word, HTML, PPT, then proposes 2-3 creative directions with photography style, mood, and visual language. After selection, generates AI image prompts and visual briefs section-by-section. Use when the user shares content and needs visual direction, image sourcing, or creative direction for any material.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2662026년 10월 19일 업데이트

Fix "Null check operator used on a null value" errors when an object is set to null during an async await. Use when: (1) Object reference is nullified while awaiting, (2) Code accesses object with ! after await returns, (3) Cancel/dispose operations run concurrently with async operations on same object. Solution: capture local reference before await.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2662026년 10월 19일 업데이트

Add custom metadata headers (x-amz-meta-*) to AWS v4 signed requests for GCS S3-compatible API. Use when: (1) Adding custom metadata to GCS uploads via S3 API, (2) Getting signature mismatch errors after adding new headers, (3) x-amz-meta-* headers being ignored or causing 403 errors. Custom headers MUST be included in canonical headers and signed headers list.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2662026년 10월 19일 업데이트

Fix password/secret authentication failures caused by trailing newlines when creating Google Cloud secrets (or similar) with bash here-strings. Use when: (1) Password authentication fails with correct password, (2) Secret created with `<<< "value"` syntax, (3) Error like "password authentication failed" or "invalid token" despite correct value. Bash here-strings (`<<<`) add a trailing newline that corrupts secrets.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2662026년 10월 19일 업데이트

Fix silent video/media processing failures caused by URL extraction code that filters on file extensions (.mp4, .webm, .webp). Use when: (1) Media moderation, transcoding, or analysis silently skips files from Blossom or content-addressed storage servers, (2) URL extraction from Nostr event tags (imeta, r tags) drops URLs without recognized extensions, (3) CDN fallback URLs append .mp4 but the actual server uses extensionless content-addressed paths like /{sha256}. Common in Nostr video events (kind 34236) where different clients use different URL formats.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2662026년 10월 19일 업데이트

divinevideo의 스킬 모두 보기

이 스킬의 문제 신고