Skip to content

fix: automation platform guard - setup and config set no longer crash on Linux#32

Open
Priyanshubhartistm wants to merge 4 commits into
AlmanacCode:mainfrom
Priyanshubhartistm:fix/automation-platform-guard
Open

fix: automation platform guard - setup and config set no longer crash on Linux#32
Priyanshubhartistm wants to merge 4 commits into
AlmanacCode:mainfrom
Priyanshubhartistm:fix/automation-platform-guard

Conversation

@Priyanshubhartistm

@Priyanshubhartistm Priyanshubhartistm commented Jul 13, 2026

Copy link
Copy Markdown

Summary

  • codealmanac config set automation.*, config apply, and setup (which reconciles automation internally) unconditionally wire LaunchdSchedulerAdapter, which shells out to launchctl. On any non-macOS platform this crashed with a raw launchctl bootstrap failed: No such file or directory error, after already writing config, and left an orphaned .plist file in ~/Library/LaunchAgents/ that nothing would ever read. Even disabling automation crashed, since uninstall()'s new launchctl-not-found path doesn't match the "service not found" markers that would otherwise let it no-op.
  • Added unavailable_reason() to the SchedulerAdapter port (mirroring the existing HarnessReadiness readiness-query pattern). AutomationService.reconcile_task()/remove_all() check it before touching the scheduler at all, so no plist is ever written and no launchctl call is attempted on an unsupported platform.

Related issues #31

Why

AutomationService's constructor in src/codealmanac/app.py has no platform check, and the recent config-first automation redesign (docs/plans/2026-07-10-user-toml-automation-preferences.md) funnels every automation-enabling path — config set, config apply, and setup's internal reconciliation — through one method, AutomationService.reconcile_task(). All three inherited the same unguarded crash.

The fix is asymmetric by design, matching how this codebase's own tests already treat these two call sites differently:

  • config set / config apply: explicit, single-purpose user commands. They keep propagating scheduler failures as a cleaExecutionFailed (existing tests like test_schtoml_for_retry already assert this is thecontract for any scheduler error) — now that failure is immediate and writes nothing, instead of a raw traceback after a half-written plist.
  • setup's internal ConfigService.update() (confirmed via grep to be SetupService's only caller — no CLI command calls
    it): a bulk onboarding operation. A structural pdn't abort instruction/harness config thatalready succeeded, so it catches the same error and reports it via a new automation_error field instead of crashing setup.
    setup still exits 0 with everything else insta

Rejected alternative

Considered putting the platform guard directly ir.install()/uninstall(). Rejected after itbroke 4 existing adapter-level unit tests (test_launchd_adapter_writes_structured_plistand others) that mocksubprocess.run directly and run on Linux CI (/launchctl mechanics independent of host OS —those tests have nothing to do with whether the real host is macOS. Moved the platform fact to a separate, non-raising
unavailable_reason() query instead, consulted utomationService`), keeping the adapter's ownmocked-subprocess tests platform-agnostic.

Tests

  • tests/test_config_service.py: config.set()/config.apply() raise ExecutionFailed cleanly with no scheduler calls when
    unavailable (UnavailableScheduler); config.upreturning automation_error` while stillpersisting other config values.
  • tests/test_setup_service.py: end-to-end testService/ConfigService stack (only the bottomSchedulerAdapterfaked) confirmingSetupService.run()` doesn't raise and reports the reason.
  • tests/test_cli.py: codealmanac setup --yesnsupported"/"macOS-only" step and noLibrary/LaunchAgents directory; codealmanac config set automation.sync.every 8h exits 1 with a clean message and writes
    nothing; codealmanac uninstall --yes still sucnavailable.

Verification

uv sync --locked
uv run ruff check .
uv run pytest
uv run codealmanac --help
uv build --out-dir dist
uvx twine check dist/*
git diff --check

All pass (471 tests). Also manually verified agaerAdapteron a Linux machine:configset/config applyfail cleanly with no orphaned plist,automation status` still works read-only.

Docs and wiki

  • README/docs updated if user-facing behavior changed.
  • .almanac/ wiki updated if an implementatiant, or gotcha changed.
  • Not applicable.

Wiki prose is written through ingest/garden lifecycle operations per this repo's convention, not hand-edited in a feature
PR; the existing wiki pages describing the macOSurate.

Notes for reviewers

  • AutomationService.status() needed no guard —tus()/inspect()already degrade gracefully whenlaunchctlis absent (catches theOSError, returns not-loaded), so automation status` continues to work as a safe read-only command everywhere.
  • Existing scheduler test doubles (FakeSchedulerAdapter, FakeScheduler, CliSchedulerAdapter) were updated to implement the extended protocol (unavailable_reason() rell prior test behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant