Skip to content

Give the LaunchAgent the operator's state dir and config - #62

Open
ahrazzle wants to merge 1 commit into
mainfrom
fix/launch-agent-state-and-config
Open

ahrazzle wants to merge 1 commit into
mainfrom
fix/launch-agent-state-and-config

Conversation

@ahrazzle

Copy link
Copy Markdown
Contributor

Found in a third audit pass. Each change has a test that fails without it.

Changes

deploy/install.sh, deploy/ai.aska.ion.plist: the scheduled run could use a different state directory and config than the operator's shell.

  • ION_HOME was dropped. The plist's EnvironmentVariables had only PATH and HOME, and the log paths and mkdir were hard-coded to ~/.ion. The README and the guide present ION_HOME as the way to move the state. With it set, ion pause, status and execute --apply from the shell used $ION_HOME, while the 06:30 agent used ~/.ion: a second state DB, and a kill switch nobody had engaged. The installer now resolves the state dir from ION_HOME (default ~/.ion), writes it into the plist as ION_HOME, and logs there.
  • ION_CONFIG was ignored. The config now comes from the argument, else ION_CONFIG, else <state dir>/ion.yml.
  • A relative config path named a different file. [ -f "$CONFIG" ] checked it against the caller's cwd, but launchd resolves it against WorkingDirectory (the source tree), where the README's uv run ion --config ion.yml doctor step leaves a dev ion.yml. cd ~/.ion && deploy/install.sh ion.yml ran every daily run on ~/src/ion/ion.yml. The installer now stores an absolute path. Paths are also escaped for sed, so a & or | no longer corrupts the substitution.

ion/util.py (home), ion/config.py (find_config_file), deploy/install.sh: un-migrated ~/.mimar state was silently abandoned.

  • home() fell back to ~/.mimar only while the ~/.ion directory did not exist. Two things created that directory: install.sh (mkdir -p "$HOME/.ion"), and the printed migration note (mkdir -p ~/.ion && mv <legacy> ~/.ion/ion.yml).
  • Once it existed, ion switched to empty state with no message:
    • an engaged ~/.mimar/PAUSED was no longer honoured;
    • proposal numbering restarted at P-0001, so candidates were filed again as new tracker issues;
    • approvals, the ledger and the dashboard issue number were left behind.
  • Fixes:
    • The fallback now checks for ~/.ion/state.db.
    • The note moves the state as well as the config (mkdir -p ~/.ion && mv ~/.mimar/* ~/.ion/ && mv ~/.ion/mimar.yml ~/.ion/ion.yml).
    • ~/.ion/ion.yml is still found while state is in ~/.mimar.
    • install.sh refuses, before creating anything, while ~/.mimar/state.db exists and ~/.ion/state.db does not.
  • If the deployed agent was re-installed after the rename, it may already have started from fresh state. If so, ~/.mimar/state.db will still be there.

.github/workflows/ci.yml, deploy/install.sh: uv sync gains --locked. Plain uv sync silently rewrites a stale uv.lock. CI then tested, and ran the licence check on, a set of versions that was never committed, and the installer left the operator's checkout dirty.

.gitignore: ignores a root ion.yml, since the README says to cp ion.example.yml ion.yml in the clone and a config can hold a webhook URL.

README.md (Deployment): says which state dir and config the agent gets.

Validation

  • uv run pytest -q: 255 passed (250 existing, 5 new in tests/test_deploy.py). All 5 fail with the deploy/ and ion/ changes reverted.
  • The tests run the real install.sh against a temp HOME, with a stub plutil and UV=true. They then run the rendered ProgramArguments the way launchd would: in WorkingDirectory, with only the plist's environment, and with run swapped for the offline status.
  • bash -n deploy/install.sh passes, and uv sync --locked passes against the committed lock.
  • Not run on macOS: plutil and launchctl are stubbed.

Automated posting by agentic team with human oversight.


Generated by Claude Code

The plist's environment had only PATH and HOME, so ION_HOME never
reached the scheduled run. An operator with ION_HOME set paused ion
from their shell, and the 06:30 run read ~/.ion: another state DB and
a kill switch nobody had engaged. install.sh now resolves the state dir
from ION_HOME (default ~/.ion) and writes it into the plist, logs there,
and takes the config from the argument, else ION_CONFIG, else
<state dir>/ion.yml.

A relative config path passed the -f check against the caller's cwd,
then launchd resolved it against WorkingDirectory (the source tree).
install.sh now stores an absolute path, and escapes paths for sed.

util.home() fell back to ~/.mimar only while the ~/.ion directory did
not exist. install.sh's `mkdir -p ~/.ion`, and the printed migration
note, both created it and silently dropped the old state.db and an
engaged ~/.mimar/PAUSED. The fallback now looks for ~/.ion/state.db,
the note moves the state as well as the config, ~/.ion/ion.yml is found
while state is still in ~/.mimar, and install.sh refuses until the
state is moved.

CI and install.sh sync with --locked, so a stale uv.lock fails instead
of being re-resolved; a root ion.yml is ignored by git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PpXZJdMN5FExq9THy5VNg

Copy link
Copy Markdown
Contributor Author

The ci workflow on this PR ends in startup_failure with no jobs started. That is not this PR: every run in the repo has failed the same way since 2026-10-18, including the pushes of #50 and #59 to main, and the workflow files have not changed since 2026-10-13. This PR's one-word --locked change to ci.yml came after that. It looks like a repository or organisation Actions setting. Details are in #73. There is no code fix to port. Locally, the full suite passes (255), uv sync --locked passes against the committed lock, and scripts/check_licenses.py is clean.

Automated posting by agentic team with human oversight.


Generated by Claude Code

@ahrazzle

Copy link
Copy Markdown
Contributor Author

Independent review by a different agent than the drafter. Verdict: approve.

What I checked. The installer now gives the LaunchAgent the operator's state directory (ION_HOME in the plist, default ~/.ion) and an absolute config path, so ion pause from a shell and the scheduled run act on the same state.

The installer refuses to proceed while state still sits in ~/.mimar and the new state has none, with the migration command in the message. ion/util.py home() now requires state.db in ~/.ion before it stops honouring the legacy directory, so an empty ~/.ion no longer strands the kill switch. The sed substitutions in install.sh are escaped, so a path containing & or | no longer corrupts the plist. CI and the installer both pin with uv sync --locked.

Suite on the branch: 255 passed, against 250 on main. An earlier run in this review showed 19 failures, all git: 'filter-repo' is not a git command in the local sandbox. Re-run with git-filter-repo on PATH, all 19 pass. CI installs that tool explicitly, so this was an environment gap, not a defect in the PR.

This PR does not fix #10: the installer still syncs with --no-dev, so the deployed agent still lacks the graph extra. That stays open. And this PR and #61 both touch ion/config.py, in different regions: whichever merges second may need a small rebase there.

This is posted as a comment because the reviewer's account is the same GitHub account that authored the PR, so GitHub will not accept a formal approving review from it. Review is requested from the other maintainer accounts. Under the repo rule (a PR merges once a different agent than its drafter has reviewed it), this review is the cross-agent pass.

Automated posting by agentic team with human oversight.

This branch has not been deployed

No deployments
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.

2 participants