These are the traps I hit building local AI infrastructure - daemons, menu bar apps, automation scripts - on macOS with system Python.
System Python Is 3.9
macOS ships Python 3.9 via Command Line Tools at /Library/Developer/CommandLineTools/usr/bin/python3. This matters because:
- No
matchstatements (3.10+) - No
str | Noneunion syntax (3.10+) - usefrom __future__ import annotationsorOptional[str] - No
tomllib(3.11+) - usetomlipackage instead - Some packages drop 3.9 support - pyobjc-core 12.0 was yanked for Python 3.9 (but still installs and works)
If your scripts need to run without a venv (e.g., LaunchAgent daemons), you’re stuck with 3.9 unless you install a separate Python.
pip Installs to User Site
pip install --user puts packages in ~/Library/Python/3.9/lib/python/site-packages/. This is the default when not in a venv. Key implications:
- Packages here are available to all scripts using system Python
- Don’t install system-wide dependencies into a project venv - they won’t be available to LaunchAgent scripts that use system Python
- If a daemon script imports a package, it must be in the user site or the system path
LaunchAgents Can’t Access iCloud Drive
launchctl runs processes in a restricted environment that cannot access ~/Library/Mobile Documents/com~apple~CloudDocs/. Scripts referenced from iCloud-synced directories will fail silently.
Fix: Copy runtime files to a non-iCloud directory (e.g., ~/alcanah-daemon/) and point the LaunchAgent plist there. Use an install script that copies from iCloud source to the runtime location.
NSApplication Timing Trap
When building macOS menu bar apps with rumps/pyobjc:
NSApplication.sharedApplication()returnsNonebefore the run loop starts- Setting
setActivationPolicy_(1)(to hide the Dock icon) at import time does nothing - Must defer to a timer callback after the framework initializes
# Wrong - NSApp is None at this point
app = NSApplication.sharedApplication()
app.setActivationPolicy_(1) # AttributeError
# Right - defer to after run loop starts
def hide_dock_icon(_):
NSApplication.sharedApplication().setActivationPolicy_(1)
rumps.Timer(hide_dock_icon, 0.1).start()
Broader Lesson
macOS system Python is fine for lightweight automation, but the version lag and environment restrictions create subtle traps. If you’re building anything beyond simple scripts, either pin a Python version via pyenv/mise or accept the constraints and code to 3.9’s limitations explicitly.