Skip to content

fix(cli): tolerate transient launchd startup state - #72

Merged
birdmanmandbir merged 1 commit into
mainfrom
fix/daemon-launchd-startup-race
Aug 13, 2026
Merged

fix(cli): tolerate transient launchd startup state#72
birdmanmandbir merged 1 commit into
mainfrom
fix/daemon-launchd-startup-race

Conversation

@orga-agent

@orga-agent orga-agent Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Observed false failure

After brew upgrade to FlickNote 1.1.0 on macOS, flicknote daemon install failed immediately:

$ flicknote daemon install
Error: FlickNote daemon service stopped before becoming ready; run `flicknote daemon status --verbose`

Roughly two seconds later the daemon was healthy (service running, application ready, IPC protocol 4, PowerSync connected), and launchd showed one successful launch. Startup logs began about two seconds after the command's transient failure — the daemon was fine; the CLI gave up too early.

Root cause

wait_for_ready() in flicknote-cli/src/commands/daemon_lifecycle.rs treated the first transient ServiceState::Stopped observation after start() as a terminal failure. launchd can report a freshly submitted/started user agent as stopped while it is still being scheduled; the correct handling is to keep polling within the bounded SERVICE_OPERATION_TIMEOUT and only fail on timeout, exactly as already done for retryable IPC errors.

The fix

wait_for_ready() now treats Stopped as pending: it sleeps one HEALTH_POLL_INTERVAL and continues polling instead of returning an error. Nothing else changes:

  • Readiness still requires both the managed service reporting Running and a compatible daemon IPC health check succeeding (the success path is unchanged and sits inside the Running arm).
  • NotInstalled during readiness remains terminal.
  • Protocol mismatch, non-retryable IPC errors, and service-manager query failures remain immediate failures.
  • Timeout still returns FlickNote-centric guidance (flicknote daemon status --verbose); platform details stay in logs/verbose diagnostics.
  • No new sleeps outside the existing bounded polling loop; the operation timeout is unchanged.
  • No legacy sync.sock compatibility aliases or cleanup (out of scope).

Why unrelated foreground daemons still cannot satisfy readiness

While the service reports Stopped, wait_for_ready() does not even consult the IPC health probe — it continues polling the managed service state. Success is only reachable from the Running arm after a confirmed Running + healthy IPC double-check. A foreground daemon that answers IPC while the managed service stays Stopped is therefore never able to satisfy readiness; the command times out with guidance instead. The updated test start_rejects_an_unrelated_healthy_daemon_when_the_service_does_not_run asserts this: it now expects the timeout error and asserts the health probe was never polled (health.polls() == 0).

Deterministic reproduction

New test install_tolerates_transient_stopped_and_retryable_ipc_failure_before_readiness drives the production LifecycleController::install_and_wait() orchestration with a fake service manager that reports Stopped for several polls before Running, plus a retryable IPC failure before healthy IPC — it fails against the old code with the exact reported error (...service stopped before becoming ready...) and passes with the fix. Other new tests cover: NotInstalled remains terminal, protocol mismatch remains terminal, non-retryable IPC errors fail immediately; all pre-existing install/start/restart ordering tests still pass.

Verification

All commands pass on this branch:

$ cargo fmt --all --check
$ cargo test -p flicknote-cli --bin flicknote commands::daemon_lifecycle::tests
   → 19 passed; 0 failed
$ cargo test -p flicknote-cli --test daemon_process
   → 8 passed; 0 failed
$ cargo test --workspace
   → all test binaries pass (82 + 8 + 14 + 193 + 64 + 8)
$ cargo clippy --workspace --all-targets --all-features -- -D warnings
   → no warnings
$ git diff --check
   → clean

@birdmanmandbir
birdmanmandbir merged commit 7b41477 into main Aug 13, 2026
9 checks passed
@orga-agent
orga-agent Bot deleted the fix/daemon-launchd-startup-race branch August 13, 2026 12:15
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