Repository navigation
🐛 fix(crux): /v1/health reports tmux and returns 503 when it is missing - #17382
Conversation
Refs hivecommons/crux#137 Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com>
|
Hive status — updated in place; edits do not notify. Full history is in the Activity Monitor. hive/awaiting-ci: the 18:23Z run failed because the 🐝 Hive Agent: — hive: agent=scanner backend=copilot model=claude-fable-5.1 copilot=1.0.88 Review findings from Intent alignment (read at head The diff implements the issue's suggested fix #1 as written: additive
Advisory, not blocking: the issue deferred "gate on 503 vs. report only" to maintainers; the hive took the gate option (issue comment). Both Dockerfiles install tmux, so production images are unaffected; the behaviour change lands on bare hosts only. Review findings from The 1. The supervisor restart-loops a tmux-less server instead of starting it (high). 2. crux's smoke job goes red on sync (medium). Not a defect, just a note: Body/diff match otherwise: additive field, 503 with full body, changelog fragment present. I have not changed or blocked anything. Review findings from The gate itself is implemented as described, and the tests are sound ( One consequence I think needs addressing before this lands: The supervisor turns the 503 into a kill/restart loop with an error that never names tmux (medium). Two smaller notes:
|
Open PR overlap checkBase branch: Conflicts are checked with every PR merged onto the current tip of No conflicting open PRs or same-file overlaps were found. |
There was a problem hiding this comment.
Style review (head 1351417) — approve, one advisory.
fakeTmuxDiris applied only inTestServeWithoutHostOrBackground(src/crux/serve/families_test.go:887). Other tests that assert 200 on/v1/health—serve_test.go:474,serve_test.go:490,principal_test.go:77,cli/serve_test.go:109— now depend on a real tmux on the host. CI is fine (v2-tests.yml:974installs tmux explicitly), but locally without tmux those unrelated auth/version tests fail withstatus 503 want 200and no hint. Suggest applying the same fake PATH there, or noting the host-tmux dependency infakeTmuxDir's doc comment.- Adjacent, non-blocking:
src/docs/crux-serve.md:238andsrc/docs/design/crux.md:751enumerate the health fields and don't mentiontmux/503.
— hive: agent=ci-maintainer backend=copilot model=claude-fable-5.1 copilot=1.0.88
Review follow-ups (below
|
|
Needs a decision, not a merge:
Head: verdict requires_human @1351417, 69/69 checks green, DCO yes, soak 0s of 10m0s. What to do: approve this head ( How to release it:
Clear the cause and the hive re-evaluates on its next pass; the stage moves to |
|
/lgtm Posted by @clubanderson from the Hive dashboard; the merge gate decides from here. |
|
@hivecommons-hive[bot]: you cannot LGTM your own PR. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/approve Posted by @clubanderson from the Hive dashboard; the merge gate decides from here. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: hivecommons-hive[bot] The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
There was a problem hiding this comment.
Decision needed: @clubanderson should decide whether /v1/health may return 503 when tmux is missing (which makes runtime.Supervisor kill and restart crux serve on every tmux-less host) or should only report the new tmux field and stay 200, as crux#137 left open.
Conclusion: the diff is correct for what it does, but it decides a question crux#137 explicitly left for maintainers, and the 503 has a concrete consequence beyond "probes see red". Routing for a decision rather than judging it myself.
What I verified (read at 7c08fcc):
src/crux/serve/serve.go:392-398—handleHealthnow callstmux.LookPath()(src/crux/agent/tmux/tmux.go:23-25, a plainexec.LookPath) and answers 503 with the full body when it errors.api.Health.Tmuxis additive (src/crux/api/api.go:108-110). Fine.src/crux/api/client.go:342-349—checkStatusturns any non-2xx into*StatusError, soClient.Health(:139-143) now returns an error on a tmux-less host and the caller never sees the body that says why.src/crux/api/client.go:362-379—WaitReadyonly returns nil whenHealthreturns nil. It will therefore spin untilctxexpires.src/crux/runtime/supervise.go:223-235— the supervisor's readiness probe is exactly thatWaitReady. On timeout it logscrux serve never became ready; killing, kills the child and (per the restart loop in the same file) starts it again. So on a host without tmux this PR converts "server up, agents fail to launch" into "server killed and restarted on a loop", with the tmux diagnosis discarded bycheckStatus.
That may well be the desired behaviour (fail loud, let the orchestrator surface it), but crux#137 says in so many words: "the 503 changes probe behaviour for existing deployments and the maintainers should confirm whether to gate on 503 or only report the field." The PR body does not mention that this was left open, nor the supervisor interaction above. If the gate is wanted, WaitReady probably needs to treat a 503-with-body as "reachable, degraded" rather than "not ready", or the supervisor will mask the real cause.
Also noted:
src/crux/serve/families_test.go:887—TestServeWithoutHostOrBackgroundnow needs a fake tmux on PATH to keep expecting 200, which is a small tell that the CI runners don't have tmux;TestHealthTmuxPresentIs200skips there, so the 200 path is only exercised through the fake.build-and-testandcoverageare red on this head but neither job exposed a log I could read, so I can't say whether that is the diff or infrastructure. Open question, not a finding.- The runbook row for
runbooks/agent-not-working.mdis deferred to hivecommons/crux, as the body says; nothing else insrc/docsdocuments/v1/healthstatus codes.
I have not approved, requested changes, or closed anything.
— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
|
CI triage (head 7c08fcc): hive/awaiting-ci: CI pending — sweep will merge when green. 🐝 Hive Agent: — hive: agent=scanner backend=copilot model=claude-fable-5.1 copilot=1.0.88 |
Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Looks correct for HTTP probes; one verified gap for Go callers — the 503 body's tmux field is unreachable through api.Client, so the diagnostic this PR adds never reaches the hive supervisor's log.
Read at 5afc5e6: src/crux/serve/serve.go:392-398, src/crux/api/client.go:257-289, 342-350, 362-380, src/crux/runtime/supervise.go:218-236.
Finding (medium, correctness): Client.do → checkStatus (client.go:342-350) treats any non-2xx as *StatusError and unmarshals the body into api.Error. The health body on 503 is api.Health, which has no error/code keys, so StatusError.Error() (client.go:119-124) falls through to "crux server returned HTTP 503". The tmux: "" field the PR body says is "still sent" is discarded before any Go caller sees it.
Consequence on a host without tmux, with the hive-side supervisor: supervise.go:223 polls WaitReady, which only stops early on 401 (client.go:369-372), so it loops for the full DefaultReadyTimeout (30s), then logs crux serve never became ready; killing ... err=crux server returned HTTP 503 (supervise.go:230), kills the server and restarts it. Louder than today's silent-200, which is the issue's goal, but the operator still has to guess that tmux is the cause — and this is the only route in serve/ that writes a non-api.Error body with a non-2xx status (writeError at serve.go:736 is the pattern everywhere else).
Two small options, either is fine:
- add
Error string \json:"error,omitempty"`/Codetoapi.Healthand setError: "tmux not found on PATH"on the 503 path — the existingcheckStatus` then surfaces it with no client change; or - keep the body and have
WaitReadyreturn immediately on a 503 (StatusError.Status == http.StatusServiceUnavailable) so the supervisor fails fast instead of polling 30s.
Everything else checked: tmux.LookPath (agent/tmux/tmux.go:23) is a plain exec.LookPath, so t.Setenv("PATH", …) in the tests drives it correctly; the TestServeWithoutHostOrBackground PATH fix is needed since that test asserts 200; Health is additive, no existing field moved. No /v1/health body docs exist under src/crux to update; the changelog fragment covers the user-facing change.
— hive: agent=reviewer-2 backend=copilot model=claude-fable-5.1 copilot=1.0.88
…lient api.Client.checkStatus decodes a non-2xx body as api.Error; the tmux 503 body was a plain api.Health with no error key, so StatusError.Error() fell back to "crux server returned HTTP 503" and the supervisor polled to ReadyTimeout, then killed and restarted crux serve without ever logging that tmux was the cause. - api.Health gains error/code (omitempty) sharing api.Error's wire keys, so the 503 body decodes as both. - handleHealth sets error="tmux not found on PATH: ..." and code=unavailable (new api.CodeUnavailable) on the 503 path; the 200 body is unchanged. - Tests: serve asserts the 503 body's error/code; api asserts Client.Health surfaces the message in the StatusError and that WaitReady keeps polling a 503 until ctx ends. Hive-Fix-Attempt: 1/6 Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Looks correct to me at 400a2ed; the one open finding from the previous round is resolved, and the 503 decision was taken by @clubanderson (/approve from the dashboard, 17:35Z).
What I verified in the tree:
src/crux/api/api.go:112-114—api.Healthnow carriesError/Codewith the same wire keys asapi.Error,omitempty, so a 200 body is unchanged apart from the additivetmuxfield.src/crux/serve/serve.go:392-401—handleHealthsetsError: "tmux not found on PATH: …"andCode: api.CodeUnavailableon the 503 path only.checkStatus(src/crux/api/client.go:342-350) unmarshals that body intoStatusError.Body, andStatusError.Error()(client.go:119-124) now returns the tmux message instead of"crux server returned HTTP 503". The supervisor'sWaitReadyloop (src/crux/runtime/supervise.go:223-231) therefore logs the cause when it gives up — exactly the gap flagged at5afc5e6.src/crux/api/client_test.go:518-547pins both halves: the error string names tmux, andWaitReadystill polls a 503 to deadline.src/crux/serve/health_test.go:25-35asserts the server side.
Remaining advisory, not blocking: TestHealthTmuxPresentIs200 and the pre-existing 200-asserting tests (serve/serve_test.go:490,627, serve/principal_test.go:77) depend on a real tmux on the host; CI's runner image bakes it in (#7206), so this only bites local runs. Docs follow-up (src/docs/crux-serve.md health field list) is already tracked in the review follow-ups comment.
I have not approved or merged anything.
— hive: agent=reviewer-2 backend=copilot model=claude-fable-5.1 copilot=1.0.88
|
Thank you for your contribution! Your PR has been merged. We'd love to hear how your experience was: share feedback |
Closes hivecommons/crux#137
Why
/v1/healthalways answered 200, even when tmux (which hosts every agent) is missing from PATH, so probes saw a healthy server that fails everycrux run. Crux's Go code is a sync ofsrc/crux, so the fix goes here rather than in hivecommons/crux (its Sync guard rejects Go changes).What
api.Healthgains an additivetmuxfield (path fromtmux.LookPath); existing fields unchanged.handleHealthreturns 503 (full body still sent) when tmux is missing.changelog.dfragment.Tests
serve/health_test.go:TestHealthTmuxMissingIs503(PATH without tmux → 503, empty field),TestHealthTmuxPresentIs200(skips if tmux not installed).TestServeWithoutHostOrBackgroundnow puts a fake tmux on PATH so it still expects 200.Not run locally; CI is the verdict. The
runbooks/agent-not-working.mdsymptom-table row is a follow-up in hivecommons/crux.— hive: agent=scanner backend=copilot model=claude-fable-5.1 copilot=1.0.88