Resolving macOS Sandbox Restrictions in Multi-Environment Development: A Case Study in Permission Architecture
During a recent development session, we encountered a critical lesson in cross-environment file access patterns when working with restricted local machines and remote infrastructure. This post details the specific permission boundaries we hit, how we diagnosed them, and the architectural decisions we made to work within macOS sandbox constraints while maintaining development velocity.
The Problem: Operation Not Permitted on Documents/
Our development workflow involves reading handoff files—markdown documents that sync state between agent processes running on EC2 and local development machines. The file in question was located at:
/Users/cb/Documents/repos/jada-ops/agent_handoffs/projects/jada-charter-system-2026-05-30.md
When attempting to read this file via shell commands on the local macOS machine, we consistently received Operation not permitted errors, despite the file existing and having standard POSIX permissions. This wasn't a typical permission issue—it was a sandbox boundary.
Technical Root Cause: macOS TCC and App Sandbox
macOS enforces Transparent User Consent (TCC) restrictions on certain directories, particularly in iCloud Drive and certain protected locations. When using shell tools (like cat, ls -la, head) on paths under ~/Library/Mobile Documents/com~apple~CloudDocs/ or deeply nested document structures, the kernel's App Sandbox can deny access even for locally-running processes if they lack the appropriate entitlements.
Our investigation showed that /Users/cb/Documents/repos/ is symlinked or mounted through iCloud Drive's sync mechanism, placing it behind the TCC boundary. Standard CLI tools don't have blanket access to these protected trees—they need explicit user grants or entitlements.
Diagnosis: Systematic Testing Across Paths
We followed a methodical narrowing approach:
- Local home directory access:
ls ~andpwdworked fine. - Documents folder direct listing:
ls ~/Documents/returnedOperation not permitted. - Nested path probing:
ls ~/Documents/repos/,cat ~/jada-ops-work/proposals/file.htmlall hit the same boundary. - Temp directory writes: Files written to
/tmp/succeeded—/tmp/is outside the protected namespace. - iCloud Drive direct inspection:
~/Library/Mobile Documents/com~apple~CloudDocs/jada-ops/explicitly confirmed TCC blocking.
The pattern was clear: any path transitioning through iCloud Drive-synced locations triggered the sandbox block.
Architectural Decision: SSH to Remote Infrastructure
Rather than fight the sandbox, we pivoted to our standing architecture rule documented in feedback_dev_on_ec2_only.md:
ALL DEV ON EC2 — local CLI is triage only. SSH to EC2 for all dev work.
This made sense for several reasons:
- Isolation: EC2 instances don't have TCC restrictions; they're clean Linux environments.
- Consistency: Code that works on EC2 doesn't need to account for macOS sandbox quirks.
- Scalability: Agent processes run on EC2 anyway; reading their state from the same machine avoids sync delays.
- Security: Handoff files contain deployment state; centralizing reads on the authoritative environment reduces race conditions.
Implementation: SSH-Based File Access Pattern
We established a pattern for reading remote handoff files:
ssh -o ConnectTimeout=10 ubuntu@34.239.233.28 \
"cat /home/ubuntu/agent_handoffs/projects/jada-charter-system-2026-05-30.md" \
> /tmp/handoff-local.md
Key details:
- User and host:
ubuntu@34.239.233.28(EC2 public IP from memory files) - SSH flags:
-o ConnectTimeout=10prevents hanging on unavailable hosts. - Remote command: Single
catcommand executed on the remote box—no pipes or subshells that might trigger permission issues. - Local output: Redirected to
/tmp/, which has no TCC restrictions.
We initially tried wrapping the command with timeout:
timeout 5 ssh ubuntu@34.239.233.28 "cat handoff.md" > /tmp/handoff.md
This failed because macOS doesn't ship GNU timeout by default (it has gtimeout from GNU coreutils via Homebrew, if installed). We switched to SSH's built-in timeout mechanism, which is portable.
File Organization: Working Directories
To avoid future sandbox issues, we established a working directory outside protected paths:
mkdir -p ~/jada-ops-work/proposals
cp -R ~/Documents/repos/jada-ops/proposals ~/jada-ops-work/
Now:
- Read-only repo state: Stays in
~/Documents/repos/(authoritative, rarely touched locally). - Working copies: Live in
~/jada-ops-work/, which is not synced to iCloud Drive. - Proposals and build artifacts: Copied to
~/jada-ops-work/proposals/for local iteration.
This structure respects the sandbox: we read the protected source once, then work in an unrestricted directory.
Infrastructure State: Where Files Actually Live
The session revealed our multi-environment file layout:
- Local Mac (iCloud-synced):
/Users/cb/Library/Mobile Documents/com~apple~CloudDocs/jada-ops/— sandbox-protected, read-only for CLI. - Local Mac (working):
/Users/cb/jada-ops-work/— unrestricted, for local iteration. - EC2 remote:
/home/ubuntu/agent_handoffs/projects/— authoritative handoff storage, accessible via SSH. - Lightsail remote:
/home/ubuntu/(secondary instance for search/verification tasks).
Handoff files sync from EC2 to iCloud Drive via a periodic rsync or S3 sync process (not detailed here), which is why the ~/Documents/repos/ copy exists but is read-only.
Key Learnings and Next Steps
What we're keeping