fix(cli): install the engine on the daemon path without a browser wizard (waired#835 §11) - #117
Merged
Merged
Conversation
…ard (waired#835 §11) waired#835 #115 gave the daemon path an executor-driven engine install, but gated it on setupActive — a browser wizard actually driving. Every terminal-only daemon-path install therefore still finishes with no engine: --non-interactive, --no-browser, no TTY, pressing Enter to take the terminal back, or simply not touching the browser. This is not a future problem. macOS reaches the daemon path on its DEFAULT install today: darwin_register_agent "$state_dir" # install.sh:1150 - launchctl bootstrap darwin_maybe_init "$state_dir" # install.sh:1153 - init runs here The plist that registration installs has RunAtLoad=true (service_darwin.go: "the agent starts the moment launchctl bootstrap finishes"), so the daemon is up before init runs and daemonReachable is true. The installer also stopped pre-installing the engine. So the default macOS install has been completing with no engine while its closing banner says "installed by sign-in when local inference is on". installtest cannot see it: every enroll leg forces the standalone branch via --bypass-mode, --google-sa-login, or an explicit service stop, so the daemon path has never been exercised there at all. Changes: - ensureDaemonPathEngine: install when the HOST WANTS INFERENCE, not when a wizard is driving. The condition is read from the daemon's own inference subsystem state, so it reflects what the agent decided rather than what a flag claimed: only "no_engine" installs; "disabled" and "stopped" are deliberate operator states and anything else means an engine already exists. Bounded by engineWaitForStatus so a silent daemon cannot hang init, and it never installs on a guess. - installEngineAsExecutor: extracted from the wizard path so both entry points share one claim-decide-install-report core, with only the narration differing (saying "for the setup in your browser" on a terminal-only install is confusing). - SetupState now serves state_dir unconditionally. #115 served it only alongside a desired engine, reasoning there was nothing to install otherwise; that reasoning is what this commit falsifies. Withholding the destination is exactly what would keep a terminal-only install engine-less. - applyDaemonInitInference: re-apply --inference-enabled, --share-with-mesh and --inference-bundled-model-id after a daemon-path login. LoginStartRequest carries only a control URL and a device name, so these were accepted and silently dropped — install.ps1 has been passing --inference-enabled on Windows to no effect (§11.2). Applied through the management routes that already own these three controls; no new wire. Nil means "not passed", so absence never overwrites what the host already decided, and a model is not requested on a host that just turned inference off. Every failure warns rather than failing a login that already succeeded. Ordering is load-bearing: the flags are re-applied before awaitBrowserSetup (so nothing races desired_model_id) and before waitForBundledModel (so the terminal does not wait on a download the operator asked to skip, or download the auto-selected model and only then switch). This lands before the §11.2 installer ordering flip, which puts Linux and Windows onto the same path macOS is already on. Refs: waired-ai/waired#835 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: gen16k <gen16k@gmail.com>
gen16k
added a commit
that referenced
this pull request
Jul 20, 2026
…takes the daemon path (waired#835 §11.2) `waired init` attaches to a running local agent and drives the browser-based first-run onboarding only when the daemon is already up (the daemon-path guard at cmd/waired/main.go); otherwise it falls through to the legacy standalone terminal enroll. Until now only macOS met that condition — its installer registers a RunAtLoad LaunchDaemon (darwin_register_agent) before darwin_maybe_init, so macOS init has always taken the daemon path. Linux and Windows ran `waired init` BEFORE starting the service, so init took the standalone path and the browser onboarding never engaged. Flip the order so all three OSes start the agent first: - install.sh: linux_apt_install and linux_apt_update call linux_service_up before linux_maybe_init. - debian postinst: start the unit on a FRESH install (was enable-only), so a plain `apt install` followed by a manual `sudo waired init` also takes the daemon path. Upgrade behaviour is unchanged — the restart-on-upgrade stays gated on the captured enabled-state, so an operator-stopped unit is not revived. - install.ps1: Ensure-AgentRunning moves ahead of Invoke-WairedInit, under the Background-service section. - waired-setup.iss (GUI installer, which does not run init itself): add a fresh-install `waired-agent start` [Run] step so a later `waired init` finds the daemon up, and correct the stale "started by waired init" note. Safe before sign-in: the daemon boots identity-less and idles until enrolment (#177). When the daemon is not reachable (non-systemd container, start failure) init falls back to the standalone path, so nothing regresses. On Linux the one-liner still pre-installs the engine (linux_install_ollama), so the daemon-path engine install (already merged, #117) is a no-op there; it is exercised on the `apt install` + manual-init path and on macOS. docs-site/getting-started/first-run.mdx already documents this behaviour ("a fresh install starts it first"); this brings the installers into line. The new CI installtest leg that exercises the daemon path end-to-end is deferred to a follow-up: it needs out-of-band CP login completion and edits scripts/dev/lib/installtest-enroll.sh, which is under concurrent modification in another session. Refs waired-ai/waired#835 Signed-off-by: gen16k <gen16k@gmail.com>
gen16k
added a commit
that referenced
this pull request
Jul 20, 2026
…takes the daemon path (waired#835 §11.2) (#119) `waired init` attaches to a running local agent and drives the browser-based first-run onboarding only when the daemon is already up (the daemon-path guard at cmd/waired/main.go); otherwise it falls through to the legacy standalone terminal enroll. Until now only macOS met that condition — its installer registers a RunAtLoad LaunchDaemon (darwin_register_agent) before darwin_maybe_init, so macOS init has always taken the daemon path. Linux and Windows ran `waired init` BEFORE starting the service, so init took the standalone path and the browser onboarding never engaged. Flip the order so all three OSes start the agent first: - install.sh: linux_apt_install and linux_apt_update call linux_service_up before linux_maybe_init. - debian postinst: start the unit on a FRESH install (was enable-only), so a plain `apt install` followed by a manual `sudo waired init` also takes the daemon path. Upgrade behaviour is unchanged — the restart-on-upgrade stays gated on the captured enabled-state, so an operator-stopped unit is not revived. - install.ps1: Ensure-AgentRunning moves ahead of Invoke-WairedInit, under the Background-service section. - waired-setup.iss (GUI installer, which does not run init itself): add a fresh-install `waired-agent start` [Run] step so a later `waired init` finds the daemon up, and correct the stale "started by waired init" note. Safe before sign-in: the daemon boots identity-less and idles until enrolment (#177). When the daemon is not reachable (non-systemd container, start failure) init falls back to the standalone path, so nothing regresses. On Linux the one-liner still pre-installs the engine (linux_install_ollama), so the daemon-path engine install (already merged, #117) is a no-op there; it is exercised on the `apt install` + manual-init path and on macOS. docs-site/getting-started/first-run.mdx already documents this behaviour ("a fresh install starts it first"); this brings the installers into line. The new CI installtest leg that exercises the daemon path end-to-end is deferred to a follow-up: it needs out-of-band CP login completion and edits scripts/dev/lib/installtest-enroll.sh, which is under concurrent modification in another session. Refs waired-ai/waired#835 Signed-off-by: gen16k <gen16k@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug this fixes exists today, on macOS
#115 gave the daemon path an executor-driven engine install, but gated it on
setupActive— a browser wizard actually driving. I scoped that too narrowly. Every terminal-only daemon-path install still finishes with no engine:--non-interactive,--no-browser, no TTY, pressing Enter to take the terminal back, or simply not touching the browser.And that is not a future problem waiting on the §11.2 ordering flip. macOS already takes the daemon path on its default install:
darwin_register_agentrunswaired-agent install, whose plist setsRunAtLoad=true—service_darwin.go's own comment says "the agent starts the moment launchctl bootstrap finishes". So the daemon is up before init runs,daemonReachableis true, and init takesrunInitViaDaemon. The installer also stopped pre-installing the engine (install.sh:1143-1148).Net: the default macOS install has been completing with no engine, while
darwin_next_stepsprintsinstalled by sign-in when local inference is on (sudo waired init)— the opposite of what happens.installtestcannot see this. Every enroll leg forces the standalone branch —--bypass-mode,--google-sa-login, or an explicit service stop — somain.go:325's!*googleSALoginguard means the daemon path has never been exercised by installtest at all. That coverage gap is what hid it.Evidence level: verified by reading the code path end to end. Not reproduced on a real macOS host — I don't have one here.
Changes
ensureDaemonPathEngine— install when the host wants inference, not when a wizard is driving. The condition is read from the daemon's own inference subsystem state rather than from any flag, so it reflects what the agent actually decided:subsystem_stateno_enginedisabled/stoppedengineWaitForStatusinstallEngineAsExecutor— extracted from the wizard path so both entry points share one claim → decide → install → report core. Only the narration differs; saying "for the setup in your browser" during a terminal-only install is confusing.SetupStatenow servesstate_dirunconditionally. #115 served it only alongside a desired engine, with the comment "there is nothing to install otherwise". That reasoning is precisely what this PR falsifies — a no-wizard install has no desired engine and still needs the destination. Withholding it is what would keep the install engine-less.applyDaemonInitInference— re-apply--inference-enabled,--share-with-meshand--inference-bundled-model-idafter a daemon-path login.LoginStartRequestcarries only a control URL and a device name, so these three have been accepted and silently dropped;install.ps1:1372-1373has been passing--inference-enabledon Windows to no effect (§11.2). Applied via the management routes that already own these controls — no new wire.nilmeans "not passed", so absence never overwrites what the host already decided.Ordering is load-bearing. Flags are re-applied before
awaitBrowserSetup(nothing racesdesired_model_id, per §4.2) and beforewaitForBundledModel(the terminal must not wait on a download the operator asked to skip, nor download the auto-selected model and only then switch).Tests
New (
init_daemon_inference_test.go): a 6-case table over flag re-application including "nothing passed touches nothing" and "model skipped when inference turned off"; failure-tolerance; a 5-state table for the engine decision; the give-up path against an unreachable daemon; the no-wizard install happy path; and a 6-case skip table (disabled / stopped / engine present / no state dir / wizard already claimed / older daemon).daemonPathEngineInstalltakesgoos/elevatedas parameters per the repo's cross-OS rule, so the install arm is reachable from an unprivileged runner.Updated:
TestSetupStateOmitsStateDirWithoutDesiredEngine→TestSetupStatePublishesStateDirWithoutDesiredEngine. It pinned the assumption this PR overturns, and I rewrote its comment to say why rather than just flipping the assertion.Verification
Not verified: a real macOS install, and the actual engine download (a ~GB fetch behind an elevation gate). The installer seam is the same
installOllamathat has shipped on the interactive path.Still uncovered, deliberately: installtest has no daemon-path enroll leg. Adding one needs a login mode that reaches
runInitViaDaemon, which today means relaxing the!*googleSALoginguard or driving a browser login. That belongs with the ordering flip (PR-R2), where the daemon path becomes the default and the gap becomes urgent.Refs
🤖 Generated with Claude Code