Repository navigation
bug: setup --yes crashes on Linux because scheduled automation is macOS/launchd-only #31
Description
What happened?
The README quickstart (curl -fsSL https://codealmanac.com/install.sh | sh then codealmanac setup --yes) fails on Linux. Setup writes config, installs instructions, writes a macOS launchd plist into ~/Library/LaunchAgents/ (on a Linux box), then dies:
codealmanac: launchctl bootstrap failed for com.codealmanac.sync: [Errno 2] No such file or directory: 'launchctl'
Exit code is 1 and the machine is left in a half-configured state:
~/.codealmanac/config.toml is written, agent instructions are installed, and an orphan com.codealmanac.sync.plist sits in ~/Library/LaunchAgents/ where nothing will ever read it.
The cause: create_app() wires AutomationService with LaunchdSchedulerAdapter unconditionally (src/codealmanac/app.py), and the adapter shells out to launchctl
(src/codealmanac/integrations/automation/scheduler/launchd.py). There is no platform check anywhere in the setup/automation path. The repo's own wiki already acknowledges this: almanac/architecture/setup/automation-and-update.md says "the production setup and automation path is macOS-specific until another scheduler adapter is added and wired" — but the README, PyPI metadata
(Operating System :: OS Independent), and setup output say nothing about it.
What did you expect?
One of:
setup --yesdetects a non-macOS platform, skips scheduled automation with a clear message ("scheduled automation is macOS-only for now, skipping sync/ garden/update schedules"), and still exits 0 with everything else installed, or - a hard, early, actionable error before any files are written, plus README docs saying automation requires macOS.
Either way, no plist should be written to ~/Library/LaunchAgents/ on Linux, and the quickstart should not crash on a supported Python platform.
Reproduction
# On any Linux machine, with the default runner (codex) installed and logged in:
codealmanac setup --yes
# -> codealmanac: launchctl bootstrap failed for com.codealmanac.sync: [Errno 2] No such file or directory: 'launchctl'
# Or without any runner at all:
codealmanac automation install sync --every 5h
# -> same failure, and ~/Library/LaunchAgents/com.codealmanac.sync.plist is left behindEnvironment
- OS: Ubuntu 24.04 (Linux 6.8)
- Python version: 3.12.3
- CodeAlmanac version: 0.3.7
- Install method: local checkout (
uv run), also reproduces from PyPI install - Agent provider, if relevant: Codex
- Almanac root, if relevant:
almanac/
Logs or output
$ codealmanac automation install sync --every 5h
codealmanac: launchctl bootstrap failed for com.codealmanac.sync: [Errno 2] No such file or directory: 'launchctl'
$ echo $?
1
$ ls ~/Library/LaunchAgents
com.codealmanac.sync.plist
Extra context
The partial-state problem makes this worse than a plain unsupported-platform error: setup completes config + instructions, then fails on the first automation task, so sync never gets scheduled and the user has no idea whether the rest of setup took effect. A minimal fix (platform guard in setup task selection + README note) keeps the door open for a later cron/systemd scheduler adapter behind the same SchedulerAdapter port.
Activity
Hotragn commentedon Aug 19, 2026
I went looking for the root cause of this while testing the Python codebase on Windows, and it turns out to be one thing rather than a per-command problem. Sharing in case it helps scope a fix, since there are currently four open/closed PRs touching this and they each patch a different layer.
There is no platform gate anywhere.
sys.platformandos.namedo not appear insrc/at all, andcore/platform.pydoes not exist.app.pywires the scheduler unconditionally:and
integrations/automation/__init__.pyre-exports only that one adapter. Solaunchdis not selected on macOS — it is the only thing there is.Why Linux fails the way it does.
launchd_target()builds the service target:os.getuid()exists on Linux, so the code proceeds and shells out tolaunchctl. That binary is missing,run_launchctlcatches theOSErrorand converts it toreturncode=1. That is why this surfaces as a confusinglaunchctl bootout failed(#30) rather than a clean "scheduled automation is not supported on this platform".setup --yesleaves state behind before it fails.LaunchdSchedulerAdapter.installwrites before it boots:and
plist_path_foris unconditional:So a failed
setupon Linux/Windows still creates~/Library/LaunchAgents/com.codealmanac.*.plist. On my Windows box that is a literalC:\Users\<me>\Library\LaunchAgents\directory.services/setup/service.pyhas no scheduler references at all, so nothing gates this earlier.Windows is strictly worse than Linux here.
os.getuiddoes not exist, so:And this one cannot be caught by the existing handler:
launchd_target()is evaluated in the argument expression atlaunchd.py:69/78/91, so it raises beforerun_launchctlis entered and itsexcept OSErrornever sees it.cli/execution.pythen re-raises, giving a raw traceback and exit 1. That is 5 of the failures inuv run pyteston Windows.One more POSIX assumption in the same file, which would bite immediately after the above is fixed —
launch_pathinservices/automation/jobs.py:On Windows
os.pathsepis;, soPATH.split(":")shreds every entry at its drive colon (['C', '\Users\...\Scripts;C', ...]).On the overlapping PRs — for whoever picks this up, these all address this issue at different layers, so they are not really alternatives to each other:
SetupService(rejects unsupported platforms before mutation)AutomationService.reconcile+config setunsupported.pyscheduler adapter + acore/platform.pysystemdadapter alongsidelaunchd#51 is the only one that introduces the missing seam, and it looks like the layer the others are working around. Happy to help verify any of them on Windows if that is useful — I have the box, and I gather from #24 that the team does not.