Unifyl — a dual-pane file manager for macOS · Blog

Why does macOS think my file manager wants Photos access? Finding the guilty path with lldb

August 2026 · by the developer of Unifyl

One day my app started triggering this dialog whenever it opened the user's home folder:

"Unifyl" would like to access your Photos library.

The app is a file manager. It contains zero photo-related code. It does not link the Photos framework, it never touches .photoslibrary bundles, and the size-calculation code that was running at the time deliberately skips ~/Pictures. Yet macOS was certain.

This post is about the debugging technique that finally answered the only question that matters in this situation — which path did I touch? — because Apple's privacy subsystem will tell you everything except that.

What TCC will and won't tell you

Privacy prompts come from tccd. You can watch it live:

log stream --predicate 'subsystem == "com.apple.TCC"' --info

This gives you a lot: the service being requested (kTCCServicePhotos), your bundle id, and whether the event is a mere status check or a real prompt. Two things I learned reading this stream for hours:

So I knew what was being requested. I had no idea why. My app walks directories in a background queue to compute folder sizes; any one of thousands of paths could have been the trigger, and my mental list of "media-protected locations" — ~/Pictures, *.photoslibrary — was apparently wrong. Auditing the skip-list and finding it correct proves the list, not the bug.

The trick: the prompt freezes the guilty thread

Here is the useful property of TCC prompts: while the dialog is on screen, the syscall that triggered it does not return. The offending thread is sitting blocked inside open(), and on arm64 the first argument to a syscall — the path pointer — is still in register x0, exactly where the calling convention put it.

So, with the dialog up, don't dismiss it. Attach a debugger instead:

lldb -p $(pgrep -x Unifyl)
(lldb) thread list
# ... several threads with frame #0 = libsystem_kernel.dylib`__open ...
(lldb) thread select 9
(lldb) memory read -f s $x0
0x16dae5a30: "/Users/me/Library/Photos"

I had four worker threads parked in __open. I read x0 as a C string on each of them. All four gave the same answer:

/Users/me/Library/Photos

The culprit is not documented anywhere

~/Library/Photos. Not the .photoslibrary bundle in ~/Pictures — a plain, ordinary-looking directory under ~/Library that turns out to hold "Shared with You" photo content, and which macOS therefore guards with kTCCServicePhotos, the same protection as the photo library itself. I have not found it in any Apple documentation of photo-library locations. My background walk was faithfully descending into it, and the first open() inside raised the prompt.

The fix was one line of policy: treat ~/Library/Photos exactly like ~/Pictures — never enter it uninvited during background size walks.

Making the experiment repeatable

Two practical notes that cost me time:

TCC remembers by code identity. Once you click "Don't Allow", subsequent runs won't prompt — they are silently denied, and your reproduction is gone. Resetting with tccutil reset Photos com.your.bundle.id works, but during a long session it is faster to re-sign a throwaway copy ad hoc: codesign -f -s - --deep gives the copy a fresh identity, so every rebuild starts with a clean TCC slate. Ad-hoc signing strips entitlements, so re-add com.apple.security.get-task-allow if you still want to attach lldb to it.

The unified log buffer rolls fast. If you go looking for the tccd entry after the fact, it may already be gone. Stream it live into a file while you reproduce.

The general recipe

The register does not speculate. Four threads, one answer, bug closed — after weeks of guessing had produced nothing but a well-audited, wrong skip-list.