fix(cli): stop waired init enrolling locally when the background service isn't answering (#175) - #271
Merged
Merged
Conversation
… answering
`waired init` picked between two entirely separate enrollment
implementations from a single 1-second probe, with no user-visible signal
and no failure mode: any transport error selected the local enrollment
path. That path declares no agent capabilities — onboarding-v1/v2 and
public-share-v1 are sent only on the daemon's network-map poll — so a host
whose service failed to start enrolled "successfully" and then dead-ended
the browser wizard permanently. The fallback converted a loud, local,
fixable failure (the service didn't start) into a silent, remote,
unfixable one. The 1-second window also lost a plain start-up race: all
three installers now register and start the service immediately before
invoking init.
The probe no longer decides *which implementation runs*; it decides
whether an agent is there at all:
- chooseEnrollRoute (init_route_daemon.go) is a pure (facts) -> route
function over the four states, table-tested over every combination.
Local enrollment runs only when explicitly selected — --bypass-mode,
--google-sa-login, re-auth — and those skip the probe entirely.
- A registered service that never answers gets a 20s wait window, then
fails with `waired doctor` plus the platform's start command, instead
of downgrading. No agent at all fails too. Both messages are built by
a goos-parameterised function so all three platforms are testable
from one host.
- No new opt-in flag: Tailscale has no local-enroll flag because it has
no local enrollment, and `tailscale up` fails the same way when
tailscaled isn't answering.
Windows could never have reported "installed but not responding": the
presence check used mgr.Connect(), which opens the SCM with
SC_MANAGER_ALL_ACCESS and so fails without Administrator, reporting a
registered service as absent. It now asks for SC_MANAGER_CONNECT +
SERVICE_QUERY_STATUS, and the rights are pinned by a test (CI's Windows
runner is elevated, so an outcome-based test would prove nothing).
installtest's interactive enrol leg stopped the daemon before init to
reach the local path; with no explicit selector it now keeps the daemon
up and takes the daemon journey, which is what a real interactive install
does. bypass/oidc keep stopping it — they select locally by flag.
First of four PRs on #175; the remaining local-enroll consumers (headless
credentials, re-auth) move daemon-side before the path itself is deleted.
Refs #175
Signed-off-by: gen16k <gen16k@gmail.com>
|
📘 Docs preview for this PR: https://waired-docs--pr-271-7jxl8beb.web.app Rebuilt on each push; the preview channel auto-expires in 7 days. |
gen16k
added a commit
that referenced
this pull request
Jul 27, 2026
Nothing reaches it any more. PR-1 (#271) removed the implicit fallback, #290/#291 removed --google-sa-login and --bypass-mode, and the daemon re-auth that landed just before this removed the last selector. What is left is ~3,300 lines that cannot run. Deleted: * cmd/waired/main.go's runInitBody tail -- everything after the route switch (~410 lines), plus chooseListenAddr, printInitSuccessBox, claudeRouteEligible and startAgentServiceBestEffort. * internal/setup/init.go (Init/InitOptions/InitResult) and deploy.go's Deploy/Puller/PullEvent. What survives deploy.go is the Ollama detection both sides still use, so the file is renamed ollama_detect.go rather than left named after what it no longer does. * The CLI-local halves of the journey: offerBenchmark, ensureBundledEngine, promptInference, promptOllamaSource, applyBundledModelSelection, initStepLabels, cliPullProgressSink, flagBoolPtr, and the GNOME AppIndicator installer. * Seven `waired init` flags that existed only to steer it: --listen, --endpoint, --skip-deploy, --start-agent, --no-wait-model, --ollama-source, --reset-config. Removed outright, so a script still passing one fails at the flag rather than being quietly ignored. Both docs-site languages updated; no installer passes any of them (checked). Kept, because they are shared and easy to delete by mistake: setup.Enroll (the daemon's enrollFunc), setup.Integration (`waired link`, `waired doctor`), confirmRenew, handStateToServiceUser (also called by `runtimes install`), benchmarkWithScanner and waitForBundledModel. --skip-claude-route is NOT removed even though nothing reads it now: install.ps1:1841 passes it, so deleting the flag would fail every Windows install that used -SkipClaudeProxy. Two gaps this makes permanent, neither caused by it -- both have been unreachable for as long as routeDaemon has been the path every install takes, and both are filed rather than buried: * #294 -- the daemon-driven init never writes Claude Code managed settings, and the installers dropped their own `waired claude enable` step on the understanding that init does it. So a CLI install finishes unrouted, and --skip-claude-route opts out of something that was not going to happen. The routing sentinel does not see this: it exercises the gateway directly, not Claude Code's settings. * #295 -- nothing installs the GNOME AppIndicator host extension any more; the waired-tray package's Suggests is what is left. internal/setup's package doc described three phases run in-process by `waired init`; it now describes what the package actually is, which is the enrollment/integration/detection pieces the daemon and the CLI share. Fixes #175 Signed-off-by: gen16k <gen16k@gmail.com>
gen16k
added a commit
that referenced
this pull request
Jul 27, 2026
Nothing reaches it any more. PR-1 (#271) removed the implicit fallback, #290/#291 removed --google-sa-login and --bypass-mode, and the daemon re-auth that landed just before this removed the last selector. What is left is ~3,300 lines that cannot run. Deleted: * cmd/waired/main.go's runInitBody tail -- everything after the route switch (~410 lines), plus chooseListenAddr, printInitSuccessBox, claudeRouteEligible and startAgentServiceBestEffort. * internal/setup/init.go (Init/InitOptions/InitResult) and deploy.go's Deploy/Puller/PullEvent. What survives deploy.go is the Ollama detection both sides still use, so the file is renamed ollama_detect.go rather than left named after what it no longer does. * The CLI-local halves of the journey: offerBenchmark, ensureBundledEngine, promptInference, promptOllamaSource, applyBundledModelSelection, initStepLabels, cliPullProgressSink, flagBoolPtr, and the GNOME AppIndicator installer. * Seven `waired init` flags that existed only to steer it: --listen, --endpoint, --skip-deploy, --start-agent, --no-wait-model, --ollama-source, --reset-config. Removed outright, so a script still passing one fails at the flag rather than being quietly ignored. Both docs-site languages updated; no installer passes any of them (checked). Kept, because they are shared and easy to delete by mistake: setup.Enroll (the daemon's enrollFunc), setup.Integration (`waired link`, `waired doctor`), confirmRenew, handStateToServiceUser (also called by `runtimes install`), benchmarkWithScanner and waitForBundledModel. --skip-claude-route is NOT removed even though nothing reads it now: install.ps1:1841 passes it, so deleting the flag would fail every Windows install that used -SkipClaudeProxy. Two gaps this makes permanent, neither caused by it -- both have been unreachable for as long as routeDaemon has been the path every install takes, and both are filed rather than buried: * #294 -- the daemon-driven init never writes Claude Code managed settings, and the installers dropped their own `waired claude enable` step on the understanding that init does it. So a CLI install finishes unrouted, and --skip-claude-route opts out of something that was not going to happen. The routing sentinel does not see this: it exercises the gateway directly, not Claude Code's settings. * #295 -- nothing installs the GNOME AppIndicator host extension any more; the waired-tray package's Suggests is what is left. internal/setup's package doc described three phases run in-process by `waired init`; it now describes what the package actually is, which is the enrollment/integration/detection pieces the daemon and the CLI share. Fixes #175 Signed-off-by: gen16k <gen16k@gmail.com>
gen16k
added a commit
that referenced
this pull request
Jul 28, 2026
…#297) * refactor(cli): delete the standalone enrollment implementation (#175) Nothing reaches it any more. PR-1 (#271) removed the implicit fallback, #290/#291 removed --google-sa-login and --bypass-mode, and the daemon re-auth that landed just before this removed the last selector. What is left is ~3,300 lines that cannot run. Deleted: * cmd/waired/main.go's runInitBody tail -- everything after the route switch (~410 lines), plus chooseListenAddr, printInitSuccessBox, claudeRouteEligible and startAgentServiceBestEffort. * internal/setup/init.go (Init/InitOptions/InitResult) and deploy.go's Deploy/Puller/PullEvent. What survives deploy.go is the Ollama detection both sides still use, so the file is renamed ollama_detect.go rather than left named after what it no longer does. * The CLI-local halves of the journey: offerBenchmark, ensureBundledEngine, promptInference, promptOllamaSource, applyBundledModelSelection, initStepLabels, cliPullProgressSink, flagBoolPtr, and the GNOME AppIndicator installer. * Seven `waired init` flags that existed only to steer it: --listen, --endpoint, --skip-deploy, --start-agent, --no-wait-model, --ollama-source, --reset-config. Removed outright, so a script still passing one fails at the flag rather than being quietly ignored. Both docs-site languages updated; no installer passes any of them (checked). Kept, because they are shared and easy to delete by mistake: setup.Enroll (the daemon's enrollFunc), setup.Integration (`waired link`, `waired doctor`), confirmRenew, handStateToServiceUser (also called by `runtimes install`), benchmarkWithScanner and waitForBundledModel. --skip-claude-route is NOT removed even though nothing reads it now: install.ps1:1841 passes it, so deleting the flag would fail every Windows install that used -SkipClaudeProxy. Two gaps this makes permanent, neither caused by it -- both have been unreachable for as long as routeDaemon has been the path every install takes, and both are filed rather than buried: * #294 -- the daemon-driven init never writes Claude Code managed settings, and the installers dropped their own `waired claude enable` step on the understanding that init does it. So a CLI install finishes unrouted, and --skip-claude-route opts out of something that was not going to happen. The routing sentinel does not see this: it exercises the gateway directly, not Claude Code's settings. * #295 -- nothing installs the GNOME AppIndicator host extension any more; the waired-tray package's Suggests is what is left. internal/setup's package doc described three phases run in-process by `waired init`; it now describes what the package actually is, which is the enrollment/integration/detection pieces the daemon and the CLI share. Fixes #175 Signed-off-by: gen16k <gen16k@gmail.com> * refactor(cli): drop two comments that outlived their code helpers.go is deleted, and runInitBody is no longer the 460-line body its doc comment described. Refs #175 Signed-off-by: gen16k <gen16k@gmail.com> --------- Signed-off-by: gen16k <gen16k@gmail.com>
gen16k
added a commit
that referenced
this pull request
Jul 28, 2026
Nothing reaches it any more. PR-1 (#271) removed the implicit fallback, re-auth that landed just before this removed the last selector. What is left is ~3,300 lines that cannot run. Deleted: * cmd/waired/main.go's runInitBody tail -- everything after the route switch (~410 lines), plus chooseListenAddr, printInitSuccessBox, claudeRouteEligible and startAgentServiceBestEffort. * internal/setup/init.go (Init/InitOptions/InitResult) and deploy.go's Deploy/Puller/PullEvent. What survives deploy.go is the Ollama detection both sides still use, so the file is renamed ollama_detect.go rather than left named after what it no longer does. * The CLI-local halves of the journey: offerBenchmark, ensureBundledEngine, promptInference, promptOllamaSource, applyBundledModelSelection, initStepLabels, cliPullProgressSink, flagBoolPtr, and the GNOME AppIndicator installer. * Seven `waired init` flags that existed only to steer it: --listen, --endpoint, --skip-deploy, --start-agent, --no-wait-model, --ollama-source, --reset-config. Removed outright, so a script still passing one fails at the flag rather than being quietly ignored. Both docs-site languages updated; no installer passes any of them (checked). Kept, because they are shared and easy to delete by mistake: setup.Enroll (the daemon's enrollFunc), setup.Integration (`waired link`, `waired doctor`), confirmRenew, handStateToServiceUser (also called by `runtimes install`), benchmarkWithScanner and waitForBundledModel. --skip-claude-route is NOT removed even though nothing reads it now: install.ps1:1841 passes it, so deleting the flag would fail every Windows install that used -SkipClaudeProxy. Two gaps this makes permanent, neither caused by it -- both have been unreachable for as long as routeDaemon has been the path every install takes, and both are filed rather than buried: * #294 -- the daemon-driven init never writes Claude Code managed settings, and the installers dropped their own `waired claude enable` step on the understanding that init does it. So a CLI install finishes unrouted, and --skip-claude-route opts out of something that was not going to happen. The routing sentinel does not see this: it exercises the gateway directly, not Claude Code's settings. * #295 -- nothing installs the GNOME AppIndicator host extension any more; the waired-tray package's Suggests is what is left. internal/setup's package doc described three phases run in-process by `waired init`; it now describes what the package actually is, which is the enrollment/integration/detection pieces the daemon and the CLI share. Fixes #175 Signed-off-by: gen16k <gen16k@gmail.com>
gen16k
added a commit
that referenced
this pull request
Jul 28, 2026
Nothing reaches it any more. PR-1 (#271) removed the implicit fallback, re-auth that landed just before this removed the last selector. What is left is ~3,300 lines that cannot run. Deleted: * cmd/waired/main.go's runInitBody tail -- everything after the route switch (~410 lines), plus chooseListenAddr, printInitSuccessBox, claudeRouteEligible and startAgentServiceBestEffort. * internal/setup/init.go (Init/InitOptions/InitResult) and deploy.go's Deploy/Puller/PullEvent. What survives deploy.go is the Ollama detection both sides still use, so the file is renamed ollama_detect.go rather than left named after what it no longer does. * The CLI-local halves of the journey: offerBenchmark, ensureBundledEngine, promptInference, promptOllamaSource, applyBundledModelSelection, initStepLabels, cliPullProgressSink, flagBoolPtr, and the GNOME AppIndicator installer. * Seven `waired init` flags that existed only to steer it: --listen, --endpoint, --skip-deploy, --start-agent, --no-wait-model, --ollama-source, --reset-config. Removed outright, so a script still passing one fails at the flag rather than being quietly ignored. Both docs-site languages updated; no installer passes any of them (checked). Kept, because they are shared and easy to delete by mistake: setup.Enroll (the daemon's enrollFunc), setup.Integration (`waired link`, `waired doctor`), confirmRenew, handStateToServiceUser (also called by `runtimes install`), benchmarkWithScanner and waitForBundledModel. --skip-claude-route is NOT removed even though nothing reads it now: install.ps1:1841 passes it, so deleting the flag would fail every Windows install that used -SkipClaudeProxy. Two gaps this makes permanent, neither caused by it -- both have been unreachable for as long as routeDaemon has been the path every install takes, and both are filed rather than buried: * #294 -- the daemon-driven init never writes Claude Code managed settings, and the installers dropped their own `waired claude enable` step on the understanding that init does it. So a CLI install finishes unrouted, and --skip-claude-route opts out of something that was not going to happen. The routing sentinel does not see this: it exercises the gateway directly, not Claude Code's settings. * #295 -- nothing installs the GNOME AppIndicator host extension any more; the waired-tray package's Suggests is what is left. internal/setup's package doc described three phases run in-process by `waired init`; it now describes what the package actually is, which is the enrollment/integration/detection pieces the daemon and the CLI share. Fixes #175 Signed-off-by: gen16k <gen16k@gmail.com>
gen16k
added a commit
that referenced
this pull request
Jul 28, 2026
…#301) * refactor(cli): delete the standalone enrollment implementation (#175) Nothing reaches it any more. PR-1 (#271) removed the implicit fallback, re-auth that landed just before this removed the last selector. What is left is ~3,300 lines that cannot run. Deleted: * cmd/waired/main.go's runInitBody tail -- everything after the route switch (~410 lines), plus chooseListenAddr, printInitSuccessBox, claudeRouteEligible and startAgentServiceBestEffort. * internal/setup/init.go (Init/InitOptions/InitResult) and deploy.go's Deploy/Puller/PullEvent. What survives deploy.go is the Ollama detection both sides still use, so the file is renamed ollama_detect.go rather than left named after what it no longer does. * The CLI-local halves of the journey: offerBenchmark, ensureBundledEngine, promptInference, promptOllamaSource, applyBundledModelSelection, initStepLabels, cliPullProgressSink, flagBoolPtr, and the GNOME AppIndicator installer. * Seven `waired init` flags that existed only to steer it: --listen, --endpoint, --skip-deploy, --start-agent, --no-wait-model, --ollama-source, --reset-config. Removed outright, so a script still passing one fails at the flag rather than being quietly ignored. Both docs-site languages updated; no installer passes any of them (checked). Kept, because they are shared and easy to delete by mistake: setup.Enroll (the daemon's enrollFunc), setup.Integration (`waired link`, `waired doctor`), confirmRenew, handStateToServiceUser (also called by `runtimes install`), benchmarkWithScanner and waitForBundledModel. --skip-claude-route is NOT removed even though nothing reads it now: install.ps1:1841 passes it, so deleting the flag would fail every Windows install that used -SkipClaudeProxy. Two gaps this makes permanent, neither caused by it -- both have been unreachable for as long as routeDaemon has been the path every install takes, and both are filed rather than buried: * #294 -- the daemon-driven init never writes Claude Code managed settings, and the installers dropped their own `waired claude enable` step on the understanding that init does it. So a CLI install finishes unrouted, and --skip-claude-route opts out of something that was not going to happen. The routing sentinel does not see this: it exercises the gateway directly, not Claude Code's settings. * #295 -- nothing installs the GNOME AppIndicator host extension any more; the waired-tray package's Suggests is what is left. internal/setup's package doc described three phases run in-process by `waired init`; it now describes what the package actually is, which is the enrollment/integration/detection pieces the daemon and the CLI share. Fixes #175 Signed-off-by: gen16k <gen16k@gmail.com> * refactor(cli): drop two comments that outlived their code helpers.go is deleted, and runInitBody is no longer the 460-line body its doc comment described. Refs #175 Signed-off-by: gen16k <gen16k@gmail.com> --------- 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.
Why
waired initpicked between two entirely separate enrollment implementations from a single 1-second probe (cmd/waired/login_client.godaemonReachable), with no user-visible signal and no failure mode — any transport error selected the local enrollment path.The two are not equivalent. Agent capabilities (
onboarding-v1/v2,public-share-v1) are declared only on the daemon's network-map poll (internal/controlclient/network_map.go), whose only non-test caller is the daemon. So a host whose service failed to start enrolled "successfully" and then dead-ended the browser wizard permanently, with nothing on the machine saying why.The accurate framing of the bug: the fallback hides the fact that the background service isn't running. It converted a loud, local, fixable failure into a silent, remote, unfixable one.
The 1-second window also lost a plain start-up race — all three installers now register and start the service immediately before invoking init (
install.shlinux_service_up→linux_maybe_init,darwin_register_agent→darwin_maybe_init,install.ps1Ensure-AgentRunning→Invoke-WairedInit), so on the production journey the local path was already reachable only when something was broken.What changed
The probe no longer decides which implementation runs; it decides whether an agent is there at all.
chooseEnrollRoute(newcmd/waired/init_route_daemon.go) is a pure(facts) -> routefunction over four states —routeDaemon/routeLocal/routeAgentDown/routeAgentAbsent— table-tested over every combination. Local enrollment runs only when explicitly selected (--bypass-mode,--google-sa-login, re-auth), and those skip the probe entirely.waitDaemonReachable, moved here frominit_benchmark.go), then fails withwaired doctorplus the platform's start command instead of downgrading. No registered service and nothing answering fails too. Both messages come from agoos-parameterised function, so all three platforms are asserted from one host.tailscale upfails with an OS-specific "tailscaled doesn't appear to be running" (cmd/tailscale/cli/diag.go), and even the official container image startstailscaledfirst (cmd/containerboot/main.go). Matching that is the point of the issue.Windows presence-check fix (same PR per §Cross-OS parity)
service.Installed()usedmgr.Connect(), which opens the SCM withSC_MANAGER_ALL_ACCESSand therefore fails without Administrator — a non-elevatedwaired initsaw a registered service as absent, sorouteAgentDowncould never fire on Windows. It now asks forSC_MANAGER_CONNECT+SERVICE_QUERY_STATUS.The test pins the rights, not the outcome: CI's Windows runner is already elevated, so an outcome-based test would pass with the old code and prove nothing.
installtest
installtest-enroll.shstopped the daemon before init for every mode. Theinteractivemode carries no explicit selector, so it now keeps the daemon up and takes the daemon journey — which is what a real interactive install does anyway.bypass/oidckeep stopping it: they select the local path by flag.Verification
go vet ./...,golangci-lint run(0 issues),go test ./... -timeout 10m— greengo build -tags prod ./...+go vet -tags prod ./...+go test -tags prod ./internal/buildflag/...— greenmake verify-cross— green (the Windows change compiles and vets underGOOS=windows)docs-site:node scripts/i18n-sync.mjs --check→ 32 pairs in syncNot verified here: the end-to-end Linux/macOS/Windows behaviour on a real host with the service stopped —
installtest.ymlcovers the enrol legs, and the failure paths are unit-tested at the seam.Scope
First of four PRs on #175. Deliberately not in this one:
--auth-keyand moving the headless credentials (--bypass-mode/--google-sa-login) daemon-side — needs a control-plane change first, since neither works against a production CP today (both are compiled out with-tags prodon the CP side)--force-reauth)init_engine.go, and converging engine install onsetup_install.goThose are planned in the split-plan comment on #175 and will be re-planned once this lands.
Refs #175