macOS: default `projects_dir` is placed inside `~/Documents`, which iCloud syncs

Author: sashaturyCreated Aug 23, 2026Updated Sep 5, 2026
Labelsfeature

Verified against: PlatformIO Core 6.1.19, PlatformIO IDE for VSCode 3.3.4, macOS 26.5.2

Summary

On macOS, get_default_projects_dir() unconditionally resolves to ~/Documents/PlatformIO/Projects. When a user has iCloud Drive's "Desktop & Documents Folders" enabled — an option macOS actively prompts for during setup — that directory is managed by fileproviderd. PlatformIO then writes .pio/ build trees, hundreds of MB of churn per rebuild, into a live sync target. This produces build and toolchain failures that are hard to attribute, because the sync daemon acts after the operation reports success.

platformio/project/helpers.py:

python
def get_default_projects_dir():
    docs_dir = os.path.join(fs.expanduser("~"), "Documents")
    try:
        assert IS_WINDOWS
        ...
    except:
        if not IS_MACOS:
            try:
                docs_dir = subprocess.check_output(["xdg-user-dir", "DOCUMENTS"]) ...

macOS is the one platform with no path to a different answer: the Windows branch is behind assert IS_WINDOWS, and the xdg-user-dir branch is explicitly excluded by if not IS_MACOS.

Impact observed

An ESP32 project living at the default location hit three independent failure classes, all traced to FileProvider and all resolved by moving the repo out of ~/Documents:

  1. Code signing broken by xattr churn — FileProvider re-stamps extended attributes faster than they can be cleared, so signing an artifact built in-tree fails intermittently.
  2. Duplicate symbols at link time — iCloud conflict copies (provision 2.cpp beside provision.cpp) are picked up by build_src_filter globs and compiled as real sources.
  3. managed_components eviction races — the component manager failed with "CHANGELOG.md missing" and rmtree: Directory not empty three times in one night while iCloud evicted and re-materialised that directory mid-build.

Plus the silent cost: every hybrid rebuild pushes hundreds of MB of .pio churn through the user's iCloud quota and uplink.

Why the existing mitigations do not cover this

  • platformio-core#4497 (OneDrive, closed in 6.1.7) hardened .pio/build removal. That is the symptom; the default location is the cause, and it is still cloud-synced.
  • The setting is changeable — pio settings set projects_dir <path> — but there is no UI for it anywhere. PIO Home reads coreSettings and cannot write them: AppRPC.load_state rebuilds them per call and sets state.modified = False, the frontend deletes storage.coreSettings before app.save_state, and no settings-write RPC handler exists. The VSCode extension contributes no corresponding configuration key either. So a user who hits this must discover a CLI command that the GUI never mentions — which is consistent with the number of forum threads asking exactly this question.

Suggested fix

Any one of these would close it; they are listed cheapest-first.

  1. Detect and avoid. On macOS, check whether ~/Documents is under FileProvider management before using it as the default, and fall back to ~/PlatformIO/Projects. A cheap signal is the presence of ~/Library/Mobile Documents/com~apple~CloudDocs plus ~/Documents resolving into the FileProvider tree.
  2. Change the macOS default outright to ~/PlatformIO/Projects (or ~/Library/Application Support/PlatformIO/Projects). Existing installs are unaffected — they carry an explicit value in appstate.json.
  3. At minimum, warn once. If a build's project path is inside a synced directory, emit a one-line warning naming the risk and the pio settings set projects_dir command. This alone would have saved the debugging described above.

Additionally, surfacing projects_dir in PIO Home's UI would make (3) actionable at the moment the user reads it.

Workaround for anyone who finds this issue

bash
pio settings set projects_dir ~/YourProjects        # persists in ~/.platformio/appstate.json
export PLATFORMIO_SETTING_PROJECTS_DIR=~/YourProjects  # wins over the above; survives a settings reset

Note the env var is PLATFORMIO_SETTING_<NAME>, not PLATFORMIO_<NAME>. The path must already exist — projects_dir_validate asserts os.path.isdir on every read.

Source: platformio/platformio-core