Skip to content

feat(cli): aileron daemon start/stop/status + auto-spawn on every command#472

Merged
ALRubinger merged 2 commits intomainfrom
worktree-cli-daemon-integration
May 6, 2026
Merged

feat(cli): aileron daemon start/stop/status + auto-spawn on every command#472
ALRubinger merged 2 commits intomainfrom
worktree-cli-daemon-integration

Conversation

@ALRubinger
Copy link
Copy Markdown
Owner

Summary

Step 7 of #454. First user-visible piece of ADR-0012. CLI commands no longer require a manually-started aileron servebindingAPIBaseURL now resolves the daemon URL via spawn.Resolve (#469), which auto-spawns the daemon binary (sibling server) if none is running.

This collapses the historical (forgot to start serve) → (connection refused) → (oh right, run serve in another terminal) loop into a single command: `aileron ` Just Works.

New subcommands

  • aileron daemon start — fork-execs the daemon. Idempotent: if one is already running, prints its URL and exits 0.
  • aileron daemon stop — reads daemon.pid, sends SIGTERM, waits for daemon.json to be removed (signalling clean shutdown). Cleans up stale daemon.json if PID is gone.
  • aileron daemon status — prints URL / PID / version / started_at and a locked/unlocked vault summary (probes /v1/vault/local/status).
  • aileron stop — alias for aileron daemon stop, per the ADR.

Wiring

  • AILERON_API_URL still bypasses spawn (test/dev escape hatch). Read on every call so tests can flip behavior mid-process without resetting cache.
  • spawn.Resolve result is cached for the CLI process lifetime; repeat callers (e.g. runStatus calls bindingAPIBaseURL twice) don't reprobe.
  • Daemon binary resolves via os.Executable() + sibling lookup (server), falling back to PATH lookup.
  • Spawn failure logs to stderr and falls back to localhost:8721 so the user sees the familiar "connection refused" rather than a malformed URL.

Test plan

  • go test ./cmd/aileron/... -race is green.
  • go vet ./cmd/aileron/... is clean.
  • Coverage on the package is 84.7% (above the 80% threshold).
  • Per-function coverage on new code: runDaemon 90%, runDaemonStatus 83.3%, probeLocalVaultLocked 90.9%, bindingAPIBaseURL 85.7%, spawnResolveCached 47.4%, daemonBinaryPath 71.4%.
  • All existing CLI tests still pass — they use t.Setenv("AILERON_API_URL", ...) to point at httptest servers, and the env-override path is hit on every call so the cache doesn't interfere.
  • Acceptance tests in Umbrella: Local Daemon Architecture (ADR-0012) #454 are the end-to-end signal. Tests that fork-exec the real daemon (runDaemonStart, the SIGTERM happy-path of runDaemonStop) are 0% / 46% covered locally — the umbrella issue's Test 1, 2, 4, 8 are the canonical proof.

Notes

  • Daemon binary is still named server (build artifact at build/server). Renaming to aileron-daemon is deferred per the plan's mechanical-PR followup.
  • AILERON_VAULT_PASSPHRASE / --passphrase-file for non-TTY unlock — not in this PR. The vault unlock surface is still the daemon's /v1/vault/local/unlock endpoint (webapp modal). The env / flag for headless contexts is a follow-up if real usage shows it's needed.
  • aileron daemon start and aileron stop are intentionally not routed through bindingAPIBaseURL — they manage the daemon's lifecycle and shouldn't auto-spawn at the wrong moment.

🤖 Generated with Claude Code

…mand

First user-visible piece of ADR-0012. CLI commands no longer require
a manually-started 'aileron serve' — bindingAPIBaseURL now resolves
the daemon URL via internal/daemon/spawn.Resolve, which auto-spawns
the daemon binary (sibling 'server') if none is running.

New subcommands:
- aileron daemon start    fork-execs the daemon (idempotent)
- aileron daemon stop     SIGTERMs by PID from daemon.json, waits for
                          discovery cleanup; cleans up stale daemon.json
                          if PID is gone
- aileron daemon status   prints URL/PID/version/started_at + locked
                          or unlocked vault state if reachable
- aileron stop            alias for daemon stop, per the ADR

Wiring:
- AILERON_API_URL still bypasses spawn (test/dev escape hatch); read
  on every call so tests can flip behavior.
- spawn.Resolve result cached for the CLI process lifetime; repeat
  calls don't reprobe.
- Daemon binary resolves via os.Executable + sibling lookup, falling
  back to PATH ('server').
- Spawn failure logs to stderr and falls back to localhost:8721 so
  the user sees the familiar 'connection refused' rather than a
  malformed URL.

Refs #454.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@railway-app
Copy link
Copy Markdown

railway-app Bot commented May 6, 2026

🚅 Deployed to the aileron-pr-472 environment in aileron

1 service not affected by this PR
  • docs

@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

❌ Patch coverage is 73.28767% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.22%. Comparing base (f8ddbde) to head (25a1f51).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

❌ Your patch check has failed because the patch coverage (73.28%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##            main     #472       +/-   ##
==========================================
+ Coverage   8.45%   82.22%   +73.77%     
==========================================
  Files        212      234       +22     
  Lines      20357    24079     +3722     
==========================================
+ Hits        1721    19799    +18078     
+ Misses     18456     3144    -15312     
- Partials     180     1136      +956     
Flag Coverage Δ
integration 8.46% <ø> (+0.01%) ⬆️
unit 78.87% <73.28%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…okup

Lifts patch coverage on the Step 7 changes from ~52% to ~86%:

- spawnResolveFn package var becomes the test seam — runDaemonStart
  and spawnResolveCached both route through it so tests can substitute
  spawn.Resolve without fork-execing a real daemon binary.
- spawnResolveCached factored into spawnResolveOnce (the body, fully
  testable) and a thin sync.Once wrapper.
- New tests: runDaemonStart happy path, spawn-error propagation,
  binary-not-found; spawnResolveOnce happy path, trailing-slash trim,
  binary-missing error, spawn-error propagation; daemonBinaryPath
  sibling and PATH branches; defaultStateDir under HOME.
- runDaemonStop happy path (SIGTERM a real daemon and poll for
  cleanup) is intentionally not faked here — shell traps are flaky
  across platforms and a Go subprocess helper added go-build-in-test
  brittleness. Acceptance Test 8 in #454 is the canonical proof.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ALRubinger ALRubinger merged commit 7987efa into main May 6, 2026
9 of 10 checks passed
@railway-app railway-app Bot temporarily deployed to aileron / aileron-pr-472 May 6, 2026 07:07 Destroyed
@ALRubinger ALRubinger deleted the worktree-cli-daemon-integration branch May 6, 2026 07:11
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