{"id":30196339,"url":"https://github.com/edtadros/icloud-nosync","last_synced_at":"2026-04-05T08:34:36.210Z","repository":{"id":306340987,"uuid":"1025848704","full_name":"edtadros/icloud-nosync","owner":"edtadros","description":"A Zsh function to exclude files/folders from iCloud Drive sync on macOS.","archived":false,"fork":false,"pushed_at":"2025-07-25T00:42:49.000Z","size":11,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-25T05:00:46.006Z","etag":null,"topics":["developer-tools","file-exclusion","icloud","icloud-drive","icloud-ignore","icloud-nosync","icloud-sync","macos","nodejs","npm","shell-script","sync-exclude","sync-ignore","xattr","zsh","zshrc","zshrc-enhancements"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edtadros.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-24T22:58:10.000Z","updated_at":"2025-07-25T00:42:52.000Z","dependencies_parsed_at":"2025-07-25T05:00:49.438Z","dependency_job_id":null,"html_url":"https://github.com/edtadros/icloud-nosync","commit_stats":null,"previous_names":["edtadros/icloud-nosync"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/edtadros/icloud-nosync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edtadros%2Ficloud-nosync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edtadros%2Ficloud-nosync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edtadros%2Ficloud-nosync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edtadros%2Ficloud-nosync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edtadros","download_url":"https://codeload.github.com/edtadros/icloud-nosync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edtadros%2Ficloud-nosync/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270183606,"owners_count":24541341,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-13T02:00:09.904Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["developer-tools","file-exclusion","icloud","icloud-drive","icloud-ignore","icloud-nosync","icloud-sync","macos","nodejs","npm","shell-script","sync-exclude","sync-ignore","xattr","zsh","zshrc","zshrc-enhancements"],"created_at":"2025-08-13T05:18:04.579Z","updated_at":"2026-04-05T08:34:36.194Z","avatar_url":"https://github.com/edtadros.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# iCloud NoSync: Exclude Files/Folders from iCloud Drive Sync on macOS\n\n\u003cimg width=\"1280\" height=\"640\" alt=\"iCloudNoSync\" src=\"https://github.com/user-attachments/assets/4890deab-cb62-448a-8901-224bbe50457d\" /\u003e\n\n\n\nThis repository provides a standalone Zsh script called `nosync` that allows you to easily mark files or folders (e.g., `node_modules`) as excluded from iCloud Drive syncing. It uses an undocumented but reliable `xattr` attribute (`com.apple.fileprovider.ignore#P`) to keep items local to your Mac, preventing unnecessary uploads and saving iCloud storage.\n\nThis is particularly useful for developers with projects in iCloud-synced folders like Documents or Desktop, where large temporary folders like `node_modules` can bloat your sync.\n\n**Note**: This works on macOS Ventura, Sonoma, and Sequoia (as of July 2025). It's based on community-tested methods and is non-destructive—items can be unmarked if needed. The xattr method may not work reliably on macOS Sequoia (15.x) and later due to potential changes in iCloud behavior. Consider alternatives like renaming with `.nosync` for newer macOS versions.\n\n## Features\n- Marks existing files or folders as non-syncing by adding the com.apple.fileprovider.ignore#P xattr attribute.\n- Unmarks items (removes the attribute) to resume syncing with --unset.\n- Prompts to create missing items (as a file or directory) before marking in non-recursive mode.\n- Supports multiple items at once (e.g., `nosync node_modules dist`).\n- Recursive search (--recursive or -r) to mark/unmark all matching items, with --directory (-d) for directories (prunes subdirs after marking unless --no-prune) or --file (-f) for files.\n- Verbose mode (--verbose or -v) to list processed paths.\n- Shows a spinner indicator in recursive mode to show activity.\n- Built-in help: Run `nosync --help` for usage details.\n- Safe and reversible: Use --unset to remove the attribute.\n- Uses full paths for commands to avoid PATH issues.\n\n## Installation\n1. **Clone the Repository**:\n   ```\n   git clone https://github.com/edtadros/icloud-nosync.git\n   cd icloud-nosync\n   ```\n\n2. **Make the Script Executable**:\n   - Ensure the script is executable:\n     ```\n     chmod +x nosync.zsh\n     ```\n\n3. **Add to Your PATH**:\n   - Add the directory containing `nosync` to your PATH in `~/.zshrc` (or `~/.bash_profile` for Bash):\n     ```\n     export PATH=\"/path/to/icloud-nosync:$PATH\"\n     ```\n   - Reload your shell:\n     ```\n     source ~/.zshrc\n     ```\n   - Alternatively, move the script to a directory already in your PATH (e.g., `/usr/local/bin`):\n     ```\n     mv nosync /usr/local/bin/\n     ```\n\nIf you're using Bash (not default on macOS), the script is mostly compatible, but test thoroughly.\n\n## Usage\nRun the script from the directory containing the items.\n\n### Basic Examples\n- Mark a folder:\n  ```\n  nosync node_modules\n  ```\n- Mark a file:\n  ```\n  nosync temp.cache\n  ```\n- Mark multiple items:\n  ```\n  nosync node_modules dist build.log\n  ```\n- Unmark a folder to resume syncing:\n  ```\n  nosync --unset node_modules\n  ```\n- Recursively mark all node_modules directories:\n  ```\n  nosync -r -d node_modules\n  ```\n- Recursively unmark all temp.cache files:\n  ```\n  nosync -u -r -f temp.cache\n  ```\n- Recursively mark with verbose output:\n  ```\n  nosync -v -r -d node_modules\n  ```\n- Recursively mark without pruning subdirs:\n  ```\n  nosync -r -d --no-prune node_modules\n  ```\n\n### If an Item Doesn't Exist (Non-Recursive Mode Only)\nYou'll be prompted (only when marking, not unsetting):\n```\ntemp.cache does not exist. Create it as a (f)ile, (d)irectory, or (s)kip?\n```\n- `f` or `F`: Create as an empty file and mark it.\n- `d` or `D`: Create as a directory and mark it.\n- `s` or anything else: Skip without creating.\n\n### Help\n```\nnosync --help\n```\nOr `nosync -h` for details on syntax and behavior.\n\n### Verify\nIn Finder, marked items should show a cloud icon with a slash (local-only). Check the attribute:\n```\nxattr -l node_modules\n```\nOutput: `com.apple.fileprovider.ignore#P: 1`\n\n### Undo\nTo resume syncing, use the --unset option:\n```\nnosync --unset node_modules\n```\nOr manually:\n```\nxattr -d 'com.apple.fileprovider.ignore#P' node_modules\n```\n\n## The Code (`nosync`)\nFor reference, here's the full script:\n\n```bash\n#!/usr/bin/env zsh\n\n# Mark file(s) or folder(s) to be excluded from iCloud sync, with prompt to create missing ones\nnosync() {\n    local recursive=false\n    local is_directory=false\n    local is_file=false\n    local unset=false\n    local verbose=false\n    local no_prune=false\n\n    # Check for help flag first\n    if [[ \"$1\" == \"--help\" || \"$1\" == \"-h\" ]]; then\n        echo \"Usage: nosync [options] [item1] [item2] ...\"\n        echo \"Marks files or folders to be excluded from iCloud Drive sync.\"\n        echo \"\"\n        echo \"Options:\"\n        echo \"  --help, -h          Display this help message and exit.\"\n        echo \"  --recursive, -r     Recursively search from the current directory for items\"\n        echo \"                      matching the given names (files or folders) and mark/unmark them.\"\n        echo \"                      No creation prompt in this mode. Requires -d or -f.\"\n        echo \"  --directory, -d     With -r, target directories only (prunes subdirs after marking unless --no-prune).\"\n        echo \"  --file, -f          With -r, target files only.\"\n        echo \"  --unset, -u         Remove the exclusion attribute to allow syncing again.\"\n        echo \"  --verbose, -v       Enable detailed output, including listing processed paths.\"\n        echo \"  --no-prune          With -r -d, disable prune to process subdirs of matched directories.\"\n        echo \"\"\n        echo \"Behavior:\"\n        echo \"  - Without --unset: Adds com.apple.fileprovider.ignore#P xattr to exclude from sync.\"\n        echo \"  - With --unset: Removes the attribute to resume sync.\"\n        echo \"  - If an item does not exist (non-recursive, without --unset), prompts to create it as a file (f), directory (d), or skip (s).\"\n        echo \"  - Use 'f' or 'F' to create a file, 'd' or 'D' to create a directory, or 's' (or any other input) to skip.\"\n        echo \"  - In recursive mode, shows a spinner indicator; with --verbose, also lists paths.\"\n        echo \"  - 5-second timeout on xattr calls to prevent hangs (requires coreutils via Homebrew).\"\n        echo \"\"\n        echo \"Examples:\"\n        echo \"  nosync node_modules          # Mark node_modules folder\"\n        echo \"  nosync temp.cache            # Mark a file\"\n        echo \"  nosync node_modules dist     # Mark multiple items\"\n        echo \"  nosync -r -d node_modules    # Recursively mark all node_modules directories\"\n        echo \"  nosync -r -f temp.cache      # Recursively mark all temp.cache files\"\n        echo \"  nosync --unset node_modules  # Unmark node_modules folder\"\n        echo \"  nosync -u -r -d node_modules # Recursively unmark all node_modules directories\"\n        echo \"  nosync -v -r -d node_modules # Recursively mark with verbose output\"\n        echo \"  nosync -r -d --no-prune node_modules # Recursively mark without pruning subdirs\"\n        return 0\n    fi\n\n    # Parse options\n    while [[ $# -gt 0 ]]; do\n        case $1 in\n            --recursive|-r)\n                recursive=true\n                shift\n                ;;\n            --directory|-d)\n                is_directory=true\n                shift\n                ;;\n            --file|-f)\n                is_file=true\n                shift\n                ;;\n            --unset|-u)\n                unset=true\n                shift\n                ;;\n            --verbose|-v)\n                verbose=true\n                shift\n                ;;\n            --no-prune)\n                no_prune=true\n                shift\n                ;;\n            *)\n                break\n                ;;\n        esac\n    done\n\n    if [[ $# -eq 0 ]]; then\n        echo \"Error: No items specified. Use --help for usage.\"\n        return 1\n    fi\n\n    if $recursive; then\n        if ! $is_directory \u0026\u0026 ! $is_file; then\n            echo \"Error: -r requires -d or -f.\"\n            return 1\n        fi\n        if $is_directory \u0026\u0026 $is_file; then\n            echo \"Error: Cannot use -d and -f together.\"\n            return 1\n        fi\n        if $no_prune \u0026\u0026 ! $is_directory; then\n            echo \"Error: --no-prune requires -d.\"\n            return 1\n        fi\n    else\n        if $is_directory || $is_file; then\n            echo \"Error: -d and -f require -r.\"\n            return 1\n        fi\n        if $no_prune; then\n            echo \"Error: --no-prune requires -r -d.\"\n            return 1\n        fi\n    fi\n\n    local xattr_cmd\n    local action_msg\n    local action_present=\"Marked\"\n    local action_past=\"Marked\"\n    if $unset; then\n        xattr_cmd=\"/usr/bin/xattr -d 'com.apple.fileprovider.ignore#P' \\\"%s\\\" || echo \\\"Failed to unmark %s (error: \\$?)\\\"\"\n        action_msg=\"Unmarked %s for iCloud sync.\"\n        action_present=\"Unmarking\"\n        action_past=\"Unmarked\"\n    else\n        xattr_cmd=\"/usr/bin/xattr -w 'com.apple.fileprovider.ignore#P' 1 \\\"%s\\\" || echo \\\"Failed to mark %s (error: \\$?)\\\"\"\n        action_msg=\"Marked %s as non-syncing for iCloud.\"\n    fi\n\n    if $recursive; then\n        local -a find_args\n        find_args=( . )\n        if $is_directory; then\n            find_args+=( -type d -name )\n        elif $is_file; then\n            find_args+=( -type f -name )\n        fi\n        local prune_arg=\"\"\n        if $is_directory \u0026\u0026 ! $no_prune; then\n            prune_arg=\"-prune\"\n        fi\n        # Start spinner in background\n        spinner \u0026\n        local spinner_pid=$!\n        trap \"kill $spinner_pid 2\u003e/dev/null\" EXIT\n\n        for item in \"$@\"; do\n            local found=false\n            while IFS= read -r -d '' path; do\n                if $verbose; then\n                    echo \"Processing $path\"\n                fi\n                ( gtimeout 5 eval $(printf \"$xattr_cmd\" \"$path\" \"$path\") ) || echo \"Timed out processing $path\"\n                printf \"$action_msg\\n\" \"$path\"\n                found=true\n            done \u003c \u003c(/usr/bin/find \"${find_args[@]}\" \"$item\" $prune_arg -print0)\n            if ! $found; then\n                echo \"No matches found for $item recursively.\"\n            fi\n        done\n        kill $spinner_pid 2\u003e/dev/null\n        trap - EXIT\n        echo \"\"  # New line after spinner\n    else\n        # Non-recursive mode: process each item directly\n        for item in \"$@\"; do\n            if [ -e \"$item\" ]; then\n                ( gtimeout 5 eval $(printf \"$xattr_cmd\" \"$item\" \"$item\") ) || echo \"Timed out processing $item\"\n                printf \"$action_msg\\n\" \"$item\"\n            else\n                if $unset; then\n                    echo \"Skipped $item: Does not exist (nothing to unmark).\"\n                else\n                    read -p \"$item does not exist. Create it as a (f)ile, (d)irectory, or (s)kip? \" answer\n                    case \"$answer\" in\n                        [Ff])\n                            if /bin/touch \"$item\"; then\n                                ( gtimeout 5 eval $(printf \"$xattr_cmd\" \"$item\" \"$item\") ) || echo \"Timed out processing $item\"\n                                printf \"Created and $action_past %s as non-syncing for iCloud.\\n\" \"$item\"\n                            else\n                                echo \"Error: Could not create file $item.\"\n                                return 1\n                            fi\n                            ;;\n                        [Dd])\n                            if /bin/mkdir -p \"$item\"; then\n                                ( gtimeout 5 eval $(printf \"$xattr_cmd\" \"$item\" \"$item\") ) || echo \"Timed out processing $item\"\n                                printf \"Created and $action_past %s as non-syncing for iCloud.\\n\" \"$item\"\n                            else\n                                echo \"Error: Could not create directory $item.\"\n                                return 1\n                            fi\n                            ;;\n                        [Ss]*|*)\n                            echo \"Skipped $item (not created).\"\n                            ;;\n                    esac\n                fi\n            fi\n        done\n    fi\n}\n\n# Spinner function for recursive mode\nspinner() {\n    local spin='-\\|/'\n    local i=0\n    while true; do\n        printf \"\\rProcessing... %c\" \"${spin:i++%${#spin}:1}\"\n        sleep 0.1\n    done\n}\n\nnosync \"$@\"\n```\n\n### Installation Notes for gtimeout\nAfter `brew install coreutils`, `gtimeout` will be in `/opt/homebrew/bin/gtimeout` (on Apple Silicon) or `/usr/local/bin/gtimeout` (Intel). If your PATH includes Homebrew bins (common in .zshrc), it should work. Otherwise, add `export PATH=\"/opt/homebrew/bin:$PATH\"` to .zshrc and source it.\n\nNow, `nosync node_modules` should add the attribute and output \"Marked node_modules as non-syncing for iCloud.\" without hanging, thanks to the timeout. If it times out, the attribute won't be added, but the script will continue.\n\nIf this still doesn't work, the xattr method is not viable on your system—switch to .nosync or another approach.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedtadros%2Ficloud-nosync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedtadros%2Ficloud-nosync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedtadros%2Ficloud-nosync/lists"}