HydraIssues

Library blanks build_url when exe_path is set, so bodies cannot auto-install
done unclassified Priority: high Project: hydraexperiencelibrary Parent: #713 Reporter: 14 Sep 2026 11:48

Description

REGRESSION, introduced by us. hydraexperiencelibrary commit 30630bc "Live filter: exempt exe_path experiences from the build requirement too", tagged v0.10.11 (2026-09-05), which is the version deployed today.

The intent was correct: before it, an experience carrying an exe_path but no registered build was dropped from the live list entirely. The implementation over-corrected. It reads:

buildURL := ""
if exp.SteamAppID == "" && exp.ExePath == "" {
build := bs.GetBuild(exp.Name, exp.ProductionBuild)
if build == nil || build.BuildURL == "" { continue }
buildURL = build.BuildURL
}

so ANY experience with an exe_path is served with build_url "" even when it has a perfectly good registered build. Before 30630bc only steam_app_id was exempt, so exe_path experiences did get their build_url.

WHY THIS IS A CATCH-22, not a registration error: hydrabody needs exe_path to launch anything (installExperience in pkg/provider/experiences.go downloads and extracts to experiencesDir/ but never derives or returns an exe path; the launcher uses exp.ExePath straight from the library). So an experience must set exe_path to be launchable, and setting it now disables its own download. There is no way to register a downloadable, launchable experience today.

MEASURED IMPACT on the live list (2026-09-14), wider than hello-xr:
mercator-talks build_url EMPTY, has exe_path
rupelmonde-castle-viewer build_url EMPTY, has exe_path, build 2 registered
gallo-romeins-museum build_url EMPTY, has exe_path
hydraunrealtemplate build_url EMPTY, has exe_path
steamvr-perftest build_url EMPTY, steam_app_id (correct, no build exists)
hello-xr build_url EMPTY, has exe_path, build 1 registered
third-person-demo build_url present, NO exe_path <- the only one that still auto-installs

So no fresh body can auto-provision any of those four real titles; they only work where the build already sits on disk from before the regression. This is latent: existing bodies kept working, so nothing failed loudly.

FIX: look the build up first and emit its URL whenever one exists; fall back to the exemption only when there is nothing to download.

buildURL := ""
if build := bs.GetBuild(exp.Name, exp.ProductionBuild); build != nil && build.BuildURL != "" {
buildURL = build.BuildURL
} else if exp.SteamAppID == "" && exp.ExePath == "" {
continue
}

hydrabody already does the right thing with both fields set: if exp.ExePath exists on disk it skips the download and marks installed, otherwise it downloads build_url. So this is safe for bodies that already have the files.

VERIFY AFTER FIX: the six live entries above keep their exe_path, the four real titles gain a build_url, steamvr-perftest keeps an empty one, and a body that already has a build on disk does not re-download.


FIELD EVIDENCE 2026-09-14/15, from renaming an experience.

The hello-xr experience was renamed to hellohydra-xr (see #713). That exercise showed the regression costs more than a one-time manual install per body, because the experience slug also determines hydrabody's install directory: installExperience() extracts to experiencesDir/ and the launcher uses the library's exe_path. So renaming an experience moves its install directory.

With build_url blanked, hydrabody cannot recover from that by itself. It looks for exe_path in the new directory, does not find it, and has no URL to download, so it can neither install nor launch. The files had to be moved by hand on chunky-turnip-23:

C:\experiences\hello-xr\hellohydra_xr.exe -> C:\experiences\hellohydra-xr\hellohydra_xr.exe

With the fix in place hydrabody would have self-healed: exe_path absent on disk, so download build_url and extract to the new directory. No manual step, and no window where the experience is live in the library but broken on the body.

So the fix is worth more than it first appeared. Without it, every one of these is manual and silent:

  • onboarding any new body
  • renaming an experience
  • recovering a body whose experience directory was cleared

Confirms the affected set measured earlier: mercator-talks, rupelmonde-castle-viewer, gallo-romeins-museum, hydraunrealtemplate and now hellohydra-xr all serve build_url "" while carrying registered builds. Only third-person-demo, which has no exe_path, still auto-installs.

Unrelated observation worth recording while renaming: the library has no rename endpoint, since name is the key. The sequence that works is create the new experience, POST /builds/notify again (watch_name matches both during the overlap and returns matched: 2), set production_build and promote, then DELETE the old entry.

Comments (1)

api 2 Oct 2026 17:30

Fixed and verified in production.

hydraexperiencelibrary v0.10.13 is deployed on pi-node-004 (image, runtime, mesh health and public health all agree). The live filter now looks the build up first and emits its URL whenever one exists, falling back to the exemption only when there is nothing to download, exactly as proposed in this issue.

Measured on the live list straight after the deploy: mercator-talks, rupelmonde-castle-viewer, gallo-romeins-museum and hellohydra-xr all went from an EMPTY build_url to a populated one, and steamvr-perftest correctly stayed empty because it is a steam_app_id title with no build. Bodies can auto-install again.

The fix is resolveLiveBuildURL, extracted so it could be covered by a table test over every case named here, including the regression itself.

One thing worth recording for whoever releases this repo next: BOTH release jobs failed on the first attempt, and not because of this change. Every cederikdotcom dependency is a private repo, so go resolves them over git rather than the proxy, and the repo had no GO_PRIVATE_TOKEN. The binary job hit "could not read Username for https://github.com" and the image build hit "git: executable file not found" inside golang:1.25-alpine. Nothing in the build had changed since v0.10.11 succeeded on 2026-09-05; the dependencies went private afterwards and the library was simply not released again until now, so the breakage sat latent. Fixed by porting hydramancer's pattern (netrc + GOPRIVATE for the binary job, a BuildKit secret mount for the image) and recorded in the runbook.