Skip to content
daniel@cosenza:~/blog
macOSFix November 7, 2025 3 min read

Fixing 'Operation Not Permitted' in Terminal After a macOS Update

A script that worked fine yesterday now fails with 'Operation not permitted' after a macOS update. It's almost always Full Disk Access, not a real permissions bug.

A script, cron job, or terminal command that worked fine before a macOS update suddenly fails with Operation not permitted, even though you’re running it as your own user against your own files, and ls -la shows nothing wrong with the permissions.

Why this isn’t a real Unix permissions problem

This error, in this specific context, almost always comes from TCC (Transparency, Consent, and Control) — macOS’s privacy protection layer, independent of both standard Unix permissions and SIP. TCC gates access to specific protected locations (Desktop, Documents, Downloads, and several others) and requires the specific application accessing them — your Terminal app, or whatever runs your script — to have been explicitly granted access, regardless of what the Unix permission bits say.

Step 1: check whether the failing operation touches a TCC-protected folder

The protected locations most commonly involved are ~/Desktop, ~/Documents, ~/Downloads, and anything requiring Full Disk Access specifically (accessing Mail data, Time Machine backups, or another application’s container). If your script touches any of these, this is very likely the cause.

Step 2: grant Full Disk Access to whatever is actually running the command

Go to System Settings → Privacy & Security → Full Disk Access, and add the specific application actually executing the command — this might be Terminal.app itself, but if you’re running things via cron, launchd, or an IDE’s integrated terminal, it’s that specific process’s parent application that needs to be added, not Terminal:

# Confirm exactly what's running the command, if unsure
ps -o comm= -p $$

Step 3: for scripts run via launchd, remember the agent itself needs access too

A launchd-managed script (a LaunchAgent) runs as its own process, separate from an interactive Terminal session — granting Terminal.app Full Disk Access does not cover a script launched independently by launchd. In this case, you may need to grant access to the specific binary being executed (/bin/bash, /usr/bin/python3, or similar), which is a broader grant worth being deliberate about.

Step 4: confirm it’s actually TCC and not a genuine SIP restriction

If the path involved is under /System or another SIP-protected location rather than your own home directory, the fix is different — this isn’t a per-app grant, it’s SIP itself, and legitimately shouldn’t be worked around outside of a deliberate, understood exception (see this blog’s SIP coverage for what SIP actually protects and why).

ls -lO /path/in/question | grep restricted

The restricted flag in ls -lO output specifically indicates SIP protection rather than a TCC gate — a useful way to tell the two apart when it isn’t obvious from the path alone.

Why this specifically shows up right after an OS update

Apple has incrementally expanded which locations and operations require explicit TCC consent across macOS releases — a script that worked under an older macOS version can start failing after an update specifically because a newly-covered location or operation now requires a grant that previously wasn’t necessary at all. Checking System Settings → Privacy & Security for the relevant category (Full Disk Access, Files and Folders) after any macOS upgrade, before assuming a script itself broke, saves a lot of confused debugging.