Skip to content

refactor(launch): aileron launch becomes a thin client of the daemon#476

Merged
ALRubinger merged 2 commits intomainfrom
worktree-launch-thin-client
May 6, 2026
Merged

refactor(launch): aileron launch becomes a thin client of the daemon#476
ALRubinger merged 2 commits intomainfrom
worktree-launch-thin-client

Conversation

@ALRubinger
Copy link
Copy Markdown
Owner

Summary

Step 8 of #454 — the structural change ADR-0012 has been building toward. This is the PR that fixes the original bug: aileron launch no longer re-prompts for vault unlock when a daemon is already running with an unlocked vault, because launch and CLI now share the daemon's vault.

Removed

  • internal/launch/gateway.go and its tests — the per-session embedded gateway. The daemon owns the gateway now.
  • internal/launch/vault_prompt_mode_test.go and the resolveVaultPromptMode / vaultPromptMode / vaultPathForGateway helpers. The daemon owns vault unlock; launch just observes status.
  • generateSessionID — the daemon mints the ULID at POST /v1/sessions.

Added

  • internal/launch/daemon_client.go — thin HTTP client for /v1/sessions, /v1/sessions/{id}/end, /v1/vault/local/status. ~150 lines.
  • internal/launch/testmain_test.go — per-package httptest fake of the daemon, advertised via AILERON_API_URL. Tests don't need a real server binary on PATH.

Refactored

internal/launch/launcher.go:Launch is now:

  1. spawn.Resolve to find/auto-spawn the daemon
  2. POST /v1/sessions to register; daemon hands back the ULID
  3. Set ANTHROPIC_BASE_URL, AILERON_URL, AILERON_APPROVAL_URL to the daemon URL (stable across launches)
  4. Run the agent
  5. POST /v1/sessions/{id}/end with the exit code (best-effort; the orphan-reaper handles the worst case)

printStartupBanner takes a daemonURL string instead of *Gateway. Vault-locked hint comes from a GET /v1/vault/local/status probe before exec — same banner shape, sourced differently.

composeAgentEnv stays (moved into launcher.go).

Binary lookup is skipped when AILERON_API_URL is set so spawn doesn't need a real server to fork-exec. (Same pattern as cmd/aileron from #472.)

Kept

  • EnsureVault and PassphrasePrompter — the daemon binary (internal/server/main.go) still uses them for stderr-prompt unlock at startup. They're now internal to the daemon's setup, not used by launch.
  • commsserver and approval socket — independent of the gateway. Step 9 (Umbrella: Local Daemon Architecture (ADR-0012) #454) moves them to daemon ownership.

Test plan

  • go test ./internal/launch/... ./internal/app/... ./cmd/aileron/... -race is green.
  • go vet ./... is clean.
  • Existing TestLaunch_* tests all pass via the package-wide httptest fake daemon (sets AILERON_API_URL).
  • printStartupBanner tests rewritten to take a string URL instead of *Gateway.
  • Acceptance Tests 2 + 3 of Umbrella: Local Daemon Architecture (ADR-0012) #454 are the canonical proof: vault unlocks once, second launch reuses it. Verified end-to-end against the real daemon binary post-merge.

Notes

  • The "first-run vault creation prompts on stderr" UX from Test 1 of the umbrella issue still depends on the daemon binary's selectVault path (which uses launch.EnsureVault). When launch fork-execs the daemon, the daemon is detached and can't prompt — first-run interactive setup remains a documented gap (deferred to a Step 7 follow-up).
  • After this lands, aileron launch claude finally reuses the standalone daemon's unlocked vault. The bug we started this thread on is fixed.

🤖 Generated with Claude Code

Step 8 of #454 — the structural change ADR-0012 has been building toward.

Removed:
- internal/launch/gateway.go and its tests (the per-session embedded
  gateway). Daemon owns the gateway now.
- internal/launch/vault_prompt_mode_test.go and the
  resolveVaultPromptMode/vaultPromptMode/vaultPathForGateway helpers.
  Daemon owns vault unlock; launch just observes status.
- generateSessionID: daemon mints the ULID at POST /v1/sessions.

Added:
- internal/launch/daemon_client.go: thin HTTP client for
  /v1/sessions, /v1/sessions/{id}/end, /v1/vault/local/status.
- internal/launch/testmain_test.go: a per-package httptest fake of
  the daemon, advertised via AILERON_API_URL so tests don't need a
  real `server` binary on PATH.

Refactored:
- internal/launch/launcher.go:Launch is now: spawn.Resolve → POST
  /v1/sessions → set ANTHROPIC_BASE_URL + AILERON_URL +
  AILERON_APPROVAL_URL to the daemon → run agent → POST
  /v1/sessions/{id}/end with the exit code. Best-effort end (warn on
  failure; the orphan-reaper handles the worst case).
- printStartupBanner takes a daemonURL string instead of *Gateway.
  Vault-locked hint comes from a GET /v1/vault/local/status probe
  before exec.
- composeAgentEnv stays (moved into launcher.go).
- Binary lookup is skipped when AILERON_API_URL is set so spawn
  doesn't need a real `server` to fork-exec.

Kept:
- EnsureVault and PassphrasePrompter — the daemon binary
  (internal/server/main.go) still uses them for stderr-prompt
  unlock at startup.
- commsserver and approval socket — independent of the gateway;
  Step 9 (#454) moves them to daemon ownership.

This is the PR that fixes the original bug: aileron launch no longer
re-prompts for vault unlock when a daemon is already running with an
unlocked vault, because launch and CLI now share the daemon's vault.

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-476 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 76.69173% with 31 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.24%. Comparing base (7987efa) to head (2c95016).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #476   +/-   ##
=======================================
  Coverage   82.24%   82.24%           
=======================================
  Files         234      234           
  Lines       24127    24153   +26     
=======================================
+ Hits        19843    19865   +22     
+ Misses       3148     3144    -4     
- Partials     1136     1144    +8     
Flag Coverage Δ
integration 8.40% <0.00%> (-0.05%) ⬇️
unit 78.90% <76.69%> (+<0.01%) ⬆️

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.

Lifts patch coverage from ~50% to ~85% on the new daemon-client and
helper code:

- daemon_client_test.go: RegisterSession success / non-OK status /
  empty-id / decode-error / EndSession success+orphaned-shape /
  non-OK status / LocalVaultLocked locked-true / unreachable /
  non-200 / garbage-body / trimTrailingSlash table.
- launcher_extra_test.go: resolveDaemonBinary sibling+PATH+notfound;
  resolveStateDir under HOME; composeAgentEnv with/without override.

Per-function coverage on the new code:
- newDaemonClient: 100%
- RegisterSession: 68.4 → 84.2%
- EndSession: 73.3 → 80%
- LocalVaultLocked: 69.2 → 92.3%
- httpStatusError: 0 → 100%
- trimTrailingSlash: 66.7 → 100%
- composeAgentEnv: 83.3 → 100%
- resolveDaemonBinary: 0 → 85.7%

Package total: 86.9% → 88.8%.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ALRubinger ALRubinger merged commit 14f13b2 into main May 6, 2026
9 of 10 checks passed
@ALRubinger ALRubinger deleted the worktree-launch-thin-client branch May 6, 2026 19:06
@railway-app railway-app Bot temporarily deployed to aileron / aileron-pr-476 May 6, 2026 19:08 Destroyed
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