[Fixed] zsh: permission denied: Step-by-Step Troubleshooting Guide

Overview & Root Cause Summary: The error zsh: permission denied occurs in Z shell (the default terminal shell in macOS and widely used in Linux) when the user attempts to execute a file lacking the executable bit, attempts to run a directory as an executable, tries to redirect output into a protected system file where the shell itself lacks elevated permissions (e.g., sudo echo "..." > /etc/hosts), or encounters security restrictions such as macOS quarantine attributes or noexec filesystem mounts.

Understanding the Root Causes

  • Missing Executable Permission (+x): Newly created shell scripts, Python scripts, or downloaded binaries default to read-and-write permissions (644 or rw-r--r--), lacking execute rights.
  • Shell Output Redirection with sudo: Running sudo echo "data" > /etc/file fails because standard terminal redirection (>) is evaluated by the unprivileged user’s shell before sudo executes the command.
  • Attempting to Execute a Directory: Pasting or invoking a directory path directly (e.g., ./build/ or ~/scripts/) instead of changing into it (cd) prompts Zsh to attempt execution and fail.
  • macOS Gatekeeper Quarantine Attribute: Binaries downloaded via web browsers or curl often receive the com.apple.quarantine extended attribute, blocking local execution.
  • Filesystem Mounted with noexec: Storage partitions, USB drives, or shared virtual machine folders mounted with the noexec flag reject all binary and script execution regardless of file permissions.

Step 1: Quick Fix (Execution Permissions & sudo Redirection)

Grant the necessary executable permissions or bypass shell redirection pitfalls using tee.

# 1. Grant execute permissions to a script or binary:
chmod +x ./script.sh

# Now execute normally:
./script.sh

# 2. Fix 'sudo echo ... > /protected/file' permission denied errors:
# WRONG (fails with zsh: permission denied):
# sudo echo "127.0.0.1 dev.local" >> /etc/hosts

# CORRECT: Use 'sudo tee -a' to write with elevated privileges:
echo "127.0.0.1 dev.local" | sudo tee -a /etc/hosts

# Alternatively, run an elevated subshell:
sudo sh -c 'echo "127.0.0.1 dev.local" >> /etc/hosts'

Step 2: Resolving Directory Execution and macOS Gatekeeper Flags

Ensure you are not accidentally executing a directory and remove macOS quarantine security flags from trusted binaries.

# 1. Check if the target path is a directory:
ls -ld ./target_path
# If the output starts with 'd' (e.g., drwxr-xr-x), navigate into it rather than running it:
cd ./target_path

# 2. On macOS: Check and remove the quarantine attribute from downloaded scripts/binaries:
xattr -l ./my_custom_binary
# If 'com.apple.quarantine' is present, remove it:
xattr -d com.apple.quarantine ./my_custom_binary

# 3. Ensure the script has an explicit Shebang line:
# The first line of your script must define the interpreter, e.g.:
# #!/usr/bin/env zsh
# or
# #!/usr/bin/env bash

Step 3: Managing File Ownership and Filesystem noexec Mounts

Reclaim ownership of files created under other user contexts and verify filesystem mount options.

# 1. Change file ownership back to your current user and group:
sudo chown $(whoami):$(id -gn) ./script.sh

# 2. Inspect filesystem mount flags (Linux / macOS):
mount | grep -i noexec

# If your target directory (e.g., /tmp or /mnt/data) is mounted with 'noexec':
# Run the script by explicitly invoking the shell interpreter directly:
zsh ./script.sh
# or
bash ./script.sh
# or
python3 ./script.py

Verification & Testing Steps

Confirm that the file has appropriate permissions and executes seamlessly in your shell.

# 1. Inspect file mode bits:
ls -la ./script.sh
# Should display executable bits: -rwxr-xr-x

# 2. Test execution:
./script.sh

# 3. Check shell exit code (should return 0 upon successful completion):
echo $?

Summary Comparison Table

Scenario / Cause Diagnostic Indicator Recommended Solution Platform
Missing Execute Bit ls -l shows -rw-r--r-- chmod +x script.sh macOS & Linux
Root Redirection Conflict sudo ... > /etc/file fails echo "..." | sudo tee -a /file macOS & Linux
macOS Quarantine Block xattr -l shows quarantine xattr -d com.apple.quarantine <file> macOS
noexec Mount Partition mount contains noexec Invoke interpreter explicitly (zsh <file>) macOS & Linux

Leave a Reply

Discover more from Victor's room

Subscribe now to keep reading and get access to the full archive.

Continue reading