hb_forge_preload packager doesn't rewrite cross-package helper calls on current main — every fresh build hits undef on first /compute
Summary
Building HyperBEAM from current main (HEAD f77973b7) with the standard rebar3 as rocksdb,genesis_wasm release produces a release that fails the very first /compute call with {"details":"undef","stacktrace":"dev_scheduler_formats:assignments_to_aos2/4 [...]"}.
The root cause is in the device packaging step introduced by ad285cd5 refactor: split core forge and preloaded devices: the hb_forge_preload plugin rewrites references within a device package, but not across packages, so a device in src/preloaded/vm/ calling a helper that lives in src/preloaded/process/ ends up dispatching to a bare module name that isn't loaded at runtime.
Steps to reproduce
# Fresh box (no prior cache state)
git clone https://github.com/permaweb/HyperBEAM
cd HyperBEAM
# minimal config — just a wallet + port; no exotic Opts
cat > config.flat <<EOF
port: 10000
priv_key_location: /opt/wallet/operator.json
operator: <ADDR>
host: 0.0.0.0
hb_store: hb_store_fs
hb_store_prefix: /opt/cache
EOF
rebar3 as rocksdb,genesis_wasm release
_build/rocksdb+genesis_wasm/rel/hb/bin/hb foreground &
# Any process ID — pick one of yours
PID=Dwnuy4MbuQkgwxw4-P08wxeny2KcwCh8Kd22mehacTc
curl -sS -H 'Accept: application/json' \
"http://localhost:10000/${PID}/compute?slot=1"
Expected: the slot computes (or a clean error like "process not found").
Actual:
{
"details": "undef",
"stacktrace": "dev_scheduler_formats:assignments_to_aos2/[<<\"Dwnuy4Mb...\">>, #{0 => #{<<\"base-hashpath\">> => ..., <<\"body\">> => #{...}}}, false, #{...}]\n '_hb_device_delegated_compute_1_0_disllh6caash3xetij65zzyx4v5izmwkgye4bnp5r5pzvfh6txqq':do_compute/3 [src/preloaded/vm/dev_delegated_compute.erl:86]\n '_hb_device_delegated_compute_1_0_disllh6...':compute/3 [src/preloaded/vm/dev_delegated_compute.erl:74]\n '_hb_device_genesis_wasm_1_0_arbx7ubut5n7gz7cseag3otcpcnlfg52o4uq3bdapb6t2can32za':do_compute/3 [src/preloaded/vm/dev_genesis_wasm.erl:105]\n '_hb_device_genesis_wasm_1_0_arbx7ubu...':compute/3 [src/preloaded/vm/dev_genesis_wasm.erl:41]\n ..."
}
Diagnosis
The dev_delegated_compute device source at src/preloaded/vm/dev_delegated_compute.erl:86 makes a bare-module-name call:
do_compute(ProcID, Req, Opts) ->
Slot = hb_ao:get(<<"slot">>, Req, Opts),
{ok, AOS2 = #{ <<"body">> := Body }} =
dev_scheduler_formats:assignments_to_aos2(
ProcID,
#{ Slot => Req },
false,
Opts
),
...
After packaging, the dev_delegated_compute module is loaded at runtime as _hb_device_delegated_compute_1_0_disllh6ca... — its content-addressed alias. But dev_scheduler_formats lives in src/preloaded/process/ (a different package), and the packager:
- Does include
dev_scheduler_formats inside the preloaded-store (strings _build/preloaded-store/data.mdb | grep dev_scheduler_formats returns matches).
- Does not load it under the bare name
dev_scheduler_formats — it's only addressable via transformed names embedded in the dev_scheduler device's package.
- Does not rewrite the call site in
_hb_device_delegated_compute_...:do_compute/3 to use the transformed name.
Result at runtime: dev_scheduler_formats:assignments_to_aos2/4 → undef.
Evidence
- Build artifacts are well-formed:
_build/preloaded-store/data.mdb is 5.5 MB and _build/hb_preloaded_index.hrl defines PRELOADED_DEVICES_INDEX_MESSAGE_ID as <<"RiPmG86LjGY24y3bPOlXR909J_QUJNnwuL0Ok80HUdA">> (a non-undefined ID).
- Top-level
ebin/ has only the hb_* core modules (84 BEAMs) — none of the dev_* devices, as expected for the preload-store-driven loader.
code:which(dev_scheduler_formats) from a remote shell returns nothing — the module is genuinely not loaded under its bare name.
- All callers of
dev_scheduler_formats:assignments_to_aos2:
src/preloaded/vm/dev_delegated_compute.erl:86 — bare call, cross-package
src/preloaded/process/dev_scheduler.erl:848 — bare call, same package (probably OK)
src/preloaded/process/dev_scheduler.erl:1389 — fun reference, same package (probably OK)
Only the first one crosses a package boundary.
Why this isn't already firing for everyone
Long-running nodes built before the refactor (e.g., from ccd49590, 2026-05-05) are still on the flat layout where every dev_*.erl lived directly in src/ and was compiled to a top-level BEAM. The bare-module-name call worked because the module was loaded under that bare name. Anyone who rebuilds from current main for the first time (or provisions a new node) hits this on the first /compute.
Related downstream finding (also worth surfacing)
While debugging this, I also noticed that the genesis-wasm Node.js sidecar's src/effects/hb/index.js reads HB_URL from process.env (return { url: HB_URL } in locateProcessWith), but the launch path doesn't set it. Without HB_URL, every locateProcess call returns { url: undefined }, which surfaces as a slower failure mode (also undef-shaped) ahead of the packaging bug above. Setting HB_URL=http://localhost:10000 on the hyperbeam unit cut my compute response time from ~41s to ~11s before the packaging undef reasserted itself. Likely worth either defaulting HB_URL from the running node's port Opt or documenting in the README "Genesis WASM" section.
Versions
- HyperBEAM: HEAD
f77973b7 Merge pull request #916 from permaweb/fix/bundler-corruption-20260518
- Erlang/OTP: 27 (
erts-15.2.7.8)
- Rebar3: 3.24.0
- Build profile:
rocksdb,genesis_wasm
- Build command:
rebar3 as rocksdb,genesis_wasm release
- OS: Debian 12 (GCE
e2-standard-4)
Happy to share fuller logs / config files privately if useful.
— @codex-curator
hb_forge_preloadpackager doesn't rewrite cross-package helper calls on currentmain— every fresh build hitsundefon first/computeSummary
Building HyperBEAM from current
main(HEADf77973b7) with the standardrebar3 as rocksdb,genesis_wasm releaseproduces a release that fails the very first/computecall with{"details":"undef","stacktrace":"dev_scheduler_formats:assignments_to_aos2/4 [...]"}.The root cause is in the device packaging step introduced by
ad285cd5 refactor: split core forge and preloaded devices: thehb_forge_preloadplugin rewrites references within a device package, but not across packages, so a device insrc/preloaded/vm/calling a helper that lives insrc/preloaded/process/ends up dispatching to a bare module name that isn't loaded at runtime.Steps to reproduce
Expected: the slot computes (or a clean error like "process not found").
Actual:
{ "details": "undef", "stacktrace": "dev_scheduler_formats:assignments_to_aos2/[<<\"Dwnuy4Mb...\">>, #{0 => #{<<\"base-hashpath\">> => ..., <<\"body\">> => #{...}}}, false, #{...}]\n '_hb_device_delegated_compute_1_0_disllh6caash3xetij65zzyx4v5izmwkgye4bnp5r5pzvfh6txqq':do_compute/3 [src/preloaded/vm/dev_delegated_compute.erl:86]\n '_hb_device_delegated_compute_1_0_disllh6...':compute/3 [src/preloaded/vm/dev_delegated_compute.erl:74]\n '_hb_device_genesis_wasm_1_0_arbx7ubut5n7gz7cseag3otcpcnlfg52o4uq3bdapb6t2can32za':do_compute/3 [src/preloaded/vm/dev_genesis_wasm.erl:105]\n '_hb_device_genesis_wasm_1_0_arbx7ubu...':compute/3 [src/preloaded/vm/dev_genesis_wasm.erl:41]\n ..." }Diagnosis
The
dev_delegated_computedevice source atsrc/preloaded/vm/dev_delegated_compute.erl:86makes a bare-module-name call:After packaging, the
dev_delegated_computemodule is loaded at runtime as_hb_device_delegated_compute_1_0_disllh6ca...— its content-addressed alias. Butdev_scheduler_formatslives insrc/preloaded/process/(a different package), and the packager:dev_scheduler_formatsinside the preloaded-store (strings _build/preloaded-store/data.mdb | grep dev_scheduler_formatsreturns matches).dev_scheduler_formats— it's only addressable via transformed names embedded in thedev_schedulerdevice's package._hb_device_delegated_compute_...:do_compute/3to use the transformed name.Result at runtime:
dev_scheduler_formats:assignments_to_aos2/4→undef.Evidence
_build/preloaded-store/data.mdbis 5.5 MB and_build/hb_preloaded_index.hrldefinesPRELOADED_DEVICES_INDEX_MESSAGE_IDas<<"RiPmG86LjGY24y3bPOlXR909J_QUJNnwuL0Ok80HUdA">>(a non-undefinedID).ebin/has only thehb_*core modules (84 BEAMs) — none of thedev_*devices, as expected for the preload-store-driven loader.code:which(dev_scheduler_formats)from a remote shell returns nothing — the module is genuinely not loaded under its bare name.dev_scheduler_formats:assignments_to_aos2:Why this isn't already firing for everyone
Long-running nodes built before the refactor (e.g., from
ccd49590, 2026-05-05) are still on the flat layout where everydev_*.erllived directly insrc/and was compiled to a top-level BEAM. The bare-module-name call worked because the module was loaded under that bare name. Anyone who rebuilds from currentmainfor the first time (or provisions a new node) hits this on the first/compute.Related downstream finding (also worth surfacing)
While debugging this, I also noticed that the
genesis-wasmNode.js sidecar'ssrc/effects/hb/index.jsreadsHB_URLfromprocess.env(return { url: HB_URL }inlocateProcessWith), but the launch path doesn't set it. WithoutHB_URL, everylocateProcesscall returns{ url: undefined }, which surfaces as a slower failure mode (alsoundef-shaped) ahead of the packaging bug above. SettingHB_URL=http://localhost:10000on the hyperbeam unit cut my compute response time from ~41s to ~11s before the packagingundefreasserted itself. Likely worth either defaultingHB_URLfrom the running node'sportOpt or documenting in the README "Genesis WASM" section.Versions
f77973b7 Merge pull request #916 from permaweb/fix/bundler-corruption-20260518erts-15.2.7.8)rocksdb,genesis_wasmrebar3 as rocksdb,genesis_wasm releasee2-standard-4)Happy to share fuller logs / config files privately if useful.
— @codex-curator