The tilde test set HOME via unsafe std::env::set_var. That is sound under
nextest (process per test) but hazardous under cargo test, and the runner
is not something a unit test should depend on. expand_tilde now takes the
home directory as a parameter; parse_wait_for reads $HOME once and passes
it down.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
Six TDD tasks: schema + KDL parsing, the Waiting state and WaitInfo, a
ready module that evaluates one condition, the interruptible gate inside
do_start, CLI surfacing, and the README.
Also corrects the spec: xy-protocol has no etcetera dependency, so ~ is
expanded from $HOME; and in KDL `command`/`args` are sibling nodes inside
the wait-for block, which changes how the parser counts conditions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
Docker-backed servers lose a startup race against OrbStack at login and are
marked Failed ~15s in, because a missing docker socket burns the crash-retry
budget. Measured: OrbStack takes 18s to create its socket; the retry cap
trips at 15s.
Adds a per-server wait-for precondition (path / tcp / command) that gates
the spawn without consuming the retry budget, surfaces as a new Waiting
state, and stays interruptible by stop/shutdown.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
service-manager 0.11 writes Disabled: true into every plist carrying
KeepAlive, and our RestartPolicy::Always guarantees KeepAlive, so every
agent we installed was born disabled. launchctl load honours the key while
still exiting 0, so install reported success on an agent that would never
start — not then and not at the next login.
service::start now removes the Disabled key itself before loading, via a
pure enable_plist() that rewrites nothing when the key is absent and
preserves every other key, including the EnvironmentVariables PATH
snapshot. That makes start self-healing for plists left disabled by an
earlier build. The crate's own start() is still not used, since without
Disabled it degrades to launchctl start, which fails on an unloaded job.
Because launchctl load exits 0 on failure, start also checks a
post-condition: it asks launchd whether the job now exists and reports a
diagnostic if it does not. stop keeps no such check, since a benign unload
of an already-stopped job also prints a failure while exiting 0.
status now treats the plist on disk as the definition of installed, as the
spec says: a plist that exists but is not loaded reports stopped with its
program, PATH and snapshot date intact instead of collapsing to
not-installed with every field cleared. That is precisely the state the
Disabled bug left users in, so it is the state status most needs to
describe.
status also gains the log path the spec always listed, and the daemon's own
log is renamed xy.log -> daemon.log so a supervised server named xy cannot
share a file, and two rotation counters, with the daemon.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
Both the spec and the plan asserted that Arc<Mutex<RotatingLogWriter>>
satisfies tracing-subscriber's MakeWriter via impl MakeWriter for Arc<W>.
That impl requires &'a W: io::Write, and &Mutex<W> does not implement
io::Write. The error reached the implementer and cost a fix round before
being caught; the shipped code uses a bare Mutex.
Corrections are marked inline so the mistake stays visible rather than
being silently erased.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
Task 6 is documentation-only. A real launchd cycle test would either
hard-code the live label and risk clobbering a working installation, or
need a test-only --label flag; the manual acceptance section covers the
mechanism instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
Six TDD tasks: io::Write for RotatingLogWriter, the daemon log file and
main.rs reorder, AgentSpec plus launchd install/uninstall, status and
launchctl load/unload, the CLI verbs, and the ignored end-to-end test
plus README.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
Reading service-manager-0.11.0/src/launchd.rs invalidated three claims:
- install() always writes Disabled:true alongside KeepAlive, so a freshly
installed agent does NOT start at login. install must be followed by
start(), which strips the key and reloads.
- The crate's stop() runs `launchctl stop`, which a KeepAlive service
survives. start/stop are now implemented as launchctl load/unload on the
plist path; the crate handles install/uninstall/status only.
- status() returns Stopped(None) unconditionally on macOS, so the reason
string the spec planned to surface does not exist.
Also corrects the RestartPolicy mapping (OnFailure emits a KeepAlive
dictionary, not a boolean) and records the launchctl-print risk to verify.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
Design for `xy service install|uninstall|start|stop|status`, backed by the
service-manager crate. Covers the launchd environment problem (PATH snapshot
at install time), the daemon's own rotating log file, and the accepted
blind spot for pre-logger launch failures.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
37-task TDD-style plan across 7 phases: workspace skeleton,
xy-protocol (config/state/rpc types), xy-supervisor (state machine
with mock-driven unit tests), xy-ipc (JSON-RPC over Unix socket),
xy binary (daemon + CLI), integration tests with test-helper bins,
and polish (fmt/clippy/README).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Approved design for the MVP: single xy binary with a Cargo workspace
(xy-protocol, xy-supervisor, xy-ipc, xy), Unix socket + newline-delimited
JSON-RPC, per-server KDL configs at XDG paths (XDG on macOS too via
etcetera), supervisor-per-server task model with per-server restart policy,
log capture to disk + ring buffer + broadcast for follow.
MVP commands: daemon, list, status, start/stop/restart (name|--all),
reload, logs. Process-alive supervision only; HTTP/MCP-aware probes,
container isolation, launchd integration, and TUI deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>