The Filesystem Path and What Lives There

iCloud Drive maps to a local directory on every Mac with iCloud enabled. The canonical path is:

~/Library/Mobile Documents/

Inside that directory, each app or container gets its own subdirectory named with a reverse-DNS identifier. The user-facing iCloud Drive root - the one Finder shows as 'iCloud Drive' - is:

~/Library/Mobile Documents/com~apple~CloudDocs/

Tilde-separated dots in directory names are how macOS encodes bundle identifiers for filesystem safety. com~apple~CloudDocs is com.apple.CloudDocs. You can ls, find, rsync, and grep inside this tree exactly as you would any local directory, with caveats around files that have been evicted to the cloud.

On macOS 15 Sequoia (current as of 2026), the Mobile Documents path has not changed since its introduction. Third-party apps that opt into iCloud Drive store their containers here too. For example, a project you store in BBEdit's iCloud container lives at:

~/Library/Mobile Documents/com~apple~TextEdit/Documents/

Not every path under Mobile Documents is writable without consequences. Writing directly into an app container you do not own can confuse that app's sync state. For your own scripts and data, create structure under com~apple~CloudDocs/ directly.

ls -la ~/Library/Mobile\ Documents/com~apple~CloudDocs/

Evicted Files: The Placeholder Problem

When macOS decides local storage is constrained, or when you enable Optimize Mac Storage in System Settings, iCloud Drive evicts file contents to the cloud and leaves a local placeholder. The placeholder file has the same name and shows the correct size in Finder, but the data is not on disk. If you cat or cp an evicted file from Terminal, macOS triggers an automatic download - but the operation blocks until the download completes, which can silently stall scripts.

To detect whether a file is evicted before touching it, check its extended attributes. The com.apple.icloud.itemName xattr is present on iCloud items, and the com.apple.ubiquity.file-id xattr identifies tracked items. More useful is the com.apple.icloud.evicted xattr, which is set when the file body is not local:

If the evicted xattr exists and is non-empty, the file is a stub. You need to explicitly download it before processing. The tool for that is brctl.

xattr -l ~/Library/Mobile\ Documents/com~apple~CloudDocs/somefile.txt

brctl: The iCloud Daemon Control Binary

brctl is the command-line interface to bird, the iCloud sync daemon (bird runs as the logged-in user, not root). brctl ships with macOS and lives at /usr/bin/brctl. It has no man page but responds to brctl help.

The subcommands you will use most:

brctl download - triggers download of an evicted file or directory brctl evict - evicts a local file back to cloud-only brctl log - streams the bird sync log brctl diagnose - dumps a full iCloud diagnostic archive brctl monitor - live sync event monitor

To download a single evicted file:

brctl returns 0 on success. It does not block until the download completes by default - it schedules the download. For scripting you need to poll the xattr or use a loop. We have seen brctl download take anywhere from 2 seconds to 4 minutes on a 100 Mbit connection depending on Apple's CDN response and local bird queue depth.

To evict a file you no longer need locally:

Eviction is useful when you are scripting large archive operations and want to keep disk usage flat. Do not evict files you are about to process - sequence eviction only after you are done with a file.

brctl download ~/Library/Mobile\ Documents/com~apple~CloudDocs/project/data.tar.gz
brctl evict ~/Library/Mobile\ Documents/com~apple~CloudDocs/archive/old-backup.tar.gz
// advertisement

Checking Sync State from Shell Scripts

The most reliable way to check whether a file is fully local is to read the com.apple.icloud.evicted xattr. If it exists, the file is not local. If it is absent, the content is on disk. Here is a reusable shell function:

This function returns 0 if the file is local and ready, 1 if it is evicted. Combine it with brctl download and a polling loop for robust script behavior:

The sleep interval matters. We tested polling at 0.5s intervals on a Mac Studio M3 Ultra with a 200 Mbit symmetric connection - typical small files (under 10 MB) complete within 3-8 seconds. For large archives, set a longer timeout.

Another signal: the MDItemWhereFroms and com.apple.metadata:kMDItemWhereFroms xattrs can tell you the original download URL of a file if it was downloaded from the web and then stored in iCloud. Useful for auditing.

is_icloud_local() {
  local f="$1"
  if xattr -p com.apple.icloud.evicted "$f" &>/dev/null; then
    return 1  # evicted, not local
  fi
  return 0  # local
}

wait_for_download() {
  local f="$1"
  local timeout="${2:-120}"
  local elapsed=0
  brctl download "$f"
  while ! is_icloud_local "$f"; do
    sleep 2
    elapsed=$((elapsed + 2))
    if [ "$elapsed" -ge "$timeout" ]; then
      echo "Timeout waiting for $f" >&2
      return 1
    fi
  done
  return 0
}

Bulk Download and Pre-fetching Directories

brctl download accepts directories as well as files. When you pass a directory, it queues download of all evicted files within it recursively. This is the fastest way to pre-fetch a project tree before running a build or backup job:

The catch: brctl download on a directory returns immediately. To know when the whole subtree is local, you need to walk it. find with xattr -l is one approach:

This lists all files in the tree that still have the evicted attribute set. When this command returns no output, every file is local. Wrap this in a loop with a timeout for your CI scripts.

For large iCloud Drive trees (we tested with ~40 GB of project files), bulk download takes 15-45 minutes depending on connection and how many files are evicted. Do not run bulk downloads from ephemeral CI runners unless you have a persistent iCloud account signed into the build machine - which is a separate operational concern discussed in the automation section below.

brctl download ~/Library/Mobile\ Documents/com~apple~CloudDocs/projects/

# Check remaining evicted files
find ~/Library/Mobile\ Documents/com~apple~CloudDocs/projects/ \
  -type f \
  -exec sh -c 'xattr -p com.apple.icloud.evicted "$1" &>/dev/null && echo "$1"' _ {} \;

Reading and Writing Files Safely

Standard Unix tools work on iCloud Drive files once they are local. rsync, tar, cp, find, grep - all behave normally. The gotchas are:

1. Extended attributes. rsync does not preserve xattrs by default on macOS. Use rsync -aX or rsync -aE to preserve them. If you strip xattrs from iCloud files and then copy them back, bird may lose tracking context. In our experience, stripping xattrs from files you then restore into iCloud Drive causes bird to re-upload the file as a new item rather than update the existing one.

2. Dot-underscore files. When you copy files from iCloud Drive to an HFS+ or APFS volume not tracked by iCloud, macOS may create ._filename resource fork companions. Use dot_clean to remove these when preparing archives:

3. Hardlinks. iCloud Drive does not support hardlinks. cp -l into iCloud Drive will either fail or create copies. Symlinks inside iCloud Drive are also not synced to other devices - Apple silently converts or drops them.

4. File locking. bird holds advisory locks on files it is actively syncing. If your script opens a file for writing while bird is mid-sync, you may get a partial write. Check sync state before writing, or write to a temp file outside iCloud Drive and then move it in atomically with mv.

rsync -aXv --progress ~/projects/myapp/ \
  ~/Library/Mobile\ Documents/com~apple~CloudDocs/projects/myapp/

# After copying, clean resource fork debris
dot_clean ~/Library/Mobile\ Documents/com~apple~CloudDocs/projects/myapp/
// advertisement

Monitoring Sync Events with brctl monitor

brctl monitor streams live sync events from bird. The output format is not documented but is readable. Each line contains a timestamp, an event type (upload, download, delete, conflict), and a path. We use it during debugging to confirm that a script-triggered write is being picked up:

Common event types you will see:

- UPDATED: file changed locally, queued for upload - DOWNLOADED: file pulled from cloud - DELETED: file removed - CONFLICTED: version conflict detected - QUOTA_EXCEEDED: account over storage limit

For persistent monitoring in a production script (for example, watching a drop folder that triggers downstream processing), brctl monitor is not suitable - it is diagnostic output, not a stable API. Instead, use FSEvents via a tool like fswatch:

fswatch watches the iCloud path and calls your processor script whenever a file changes. Combine with the is_icloud_local check to ensure you only process fully-downloaded files. This pattern is what we use for light automation on developer Macs - for heavier pipeline work, purpose-built tools at taskbotshub.ai offer Mac-aware workflow automation that handles iCloud sync state as part of job pre-conditions.

brctl monitor | grep --line-buffered 'com~apple~CloudDocs'

# FSEvents-based watch for drop folder automation
fswatch -o ~/Library/Mobile\ Documents/com~apple~CloudDocs/drop/ | \
  xargs -I{} bash -c 'for f in ~/Library/Mobile\ Documents/com~apple~CloudDocs/drop/*; do wait_for_download "$f" && ./process.sh "$f"; done'

iCloud Drive Log Inspection and Troubleshooting

When sync stalls, brctl log is your first tool. It outputs bird's internal log, filterable by level:

The output is verbose. Filter for errors and warnings:

For deeper investigation, brctl diagnose creates a compressed archive with the full sync state, recent logs, and account info (no credentials). The archive lands in ~/Desktop/ by default:

We have submitted brctl diagnose archives to Apple feedback when encountering persistent sync failures on M3 hardware. The archive is typically 5-20 MB compressed.

Another useful inspection: the bird process itself. Check its CPU and memory with:

bird should idle near 0% CPU. Sustained high CPU from bird (above 10% for more than a few minutes) indicates a sync conflict loop or a corrupted local database. The nuclear fix for a stuck bird is:

This kills bird (macOS restarts it automatically via launchd), clears the local sync database, and forces a full re-sync. You will not lose data - iCloud retains the canonical copies. But re-sync of a large library can take hours. Only do this when standard troubleshooting has failed.

# Stream recent log
brctl log --level debug 2>&1 | grep -i 'error\|warn\|conflict'

# Full diagnostic archive
brctl diagnose -o ~/Desktop/icloud-diag.tar.gz

# Check bird resource usage
top -pid $(pgrep bird) -l 5 -stats pid,cpu,mem,time

# Reset bird (last resort)
killall bird
rm -rf ~/Library/Application\ Support/CloudDocs/session/

Scripting Backups and Archives from iCloud Drive

A common sysadmin need: back up iCloud Drive to a NAS, S3, or local external drive on a schedule. The naive approach - just rsync the Mobile Documents directory - works but has two failure modes: evicted files produce empty stubs in the backup, and the com~apple~CloudDocs directory name can confuse some backup tools.

Robust approach: pre-fetch, then rsync.

Step 1: download all evicted files. Step 2: wait for completion. Step 3: rsync to destination.

For S3 backup, aws s3 sync works directly against the iCloud path. Use --no-follow-symlinks since symlinks do not sync across devices anyway:

For scheduled execution, a launchd plist is more reliable than cron on macOS. Create a plist in ~/Library/LaunchAgents/:

Load it with launchctl load -w ~/Library/LaunchAgents/org.myunix.icloud-backup.plist. The StartCalendarInterval key sets the schedule - this example runs at 02:00 daily.

If you name your backup scripts or project directories consistently (something you might think through at a service like nicename.me for domain or project identity), your launchd labels, log paths, and script names stay coherent across multiple Macs in a fleet.

#!/bin/bash
# icloud-backup.sh
ICLOUD=~/Library/Mobile\ Documents/com~apple~CloudDocs
DEST=/Volumes/NAS/icloud-backup

echo "[$(date)] Pre-fetching evicted files..."
brctl download "$ICLOUD"
sleep 30  # give bird time to queue

# Wait until no evicted files remain
while find "$ICLOUD" -type f -exec sh -c \
  'xattr -p com.apple.icloud.evicted "$1" &>/dev/null && exit 1' _ {} \; 2>/dev/null | grep -q .; do
  echo "[$(date)] Still downloading, waiting 30s..."
  sleep 30
done

echo "[$(date)] All files local. Starting rsync..."
rsync -aX --delete --exclude '.DS_Store' \
  "$ICLOUD/" "$DEST/"
echo "[$(date)] Backup complete."
// advertisement

Managing iCloud Drive from Remote SSH Sessions

iCloud Drive is tied to the logged-in user session via bird, which runs as a user-space daemon. If you SSH into a Mac and the console user is logged in with iCloud active, you can interact with the iCloud Drive path directly - brctl commands work over SSH because bird is already running.

What does not work over SSH: if no user is logged into the GUI session (bare SSH with no active Aqua login), bird is not running and the iCloud Drive path either does not exist or contains only stale local data. This is a fundamental constraint - iCloud on macOS requires an active GUI session to sync.

Workaround for headless Mac servers: enable auto-login in System Settings, and use a launchd agent (not a daemon) so the job runs in the user session context. LaunchAgents run after GUI login; LaunchDaemons run as root before login and cannot use iCloud.

To verify bird is running before your remote script depends on it:

If pgrep returns nothing, bird is not running and iCloud Drive operations will stall silently. Exit your script early in that case rather than letting it hang.

# Check bird is running (required for iCloud ops)
if ! pgrep -u "$USER" bird > /dev/null 2>&1; then
  echo "bird not running - iCloud Drive unavailable" >&2
  exit 1
fi

# Confirm iCloud path exists and is accessible
if [ ! -d ~/Library/Mobile\ Documents/com~apple~CloudDocs ]; then
  echo "iCloud Drive path not found" >&2
  exit 1
fi

Practical Limitations and Edge Cases

File name limits: iCloud Drive enforces stricter naming than APFS locally. Files with colons, leading dots in certain contexts, or names exceeding 255 bytes may fail to sync even if they sit happily on a local APFS volume. You will not always get an error - the file may simply stop syncing silently.

Conflict files: when the same file is edited on two devices before either syncs, bird creates a conflict copy named originalfile (name's conflicted copy 2026-08-23).ext. These accumulate. Find and review them:

Storage quota: iCloud Drive operations fail silently when the account hits storage quota. brctl monitor will show QUOTA_EXCEEDED events. There is no command-line tool to check iCloud quota directly - you need to query it via the iCloud API or check System Settings. Script defensively by checking available iCloud storage through system_profiler or the iCloud APIs if your tooling requires it.

Sandbox restrictions: if you run your scripts from within a sandboxed app context, you may not have read access to other apps' iCloud containers. Terminal.app itself is not sandboxed, so scripts run from Terminal have normal user-level file access. Scripts run via Automator or Shortcuts may be sandboxed.

EnterpriseConnect / MDM: in managed environments, iCloud Drive may be restricted by MDM policy. Check with profiles list -verbose and look for CloudDocument restrictions in the payload output.

# Find iCloud conflict copies
find ~/Library/Mobile\ Documents/com~apple~CloudDocs/ \
  -name "*conflicted copy*" -type f

# Check MDM profiles for iCloud restrictions
profiles list -verbose 2>/dev/null | grep -i 'cloud\|icloud'