Skip to content

hb_forge_preload packager doesn't rewrite cross-package helper calls on current main — fresh builds hit undef on first /compute #944

Description

@codex-curator

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:

  1. Does include dev_scheduler_formats inside the preloaded-store (strings _build/preloaded-store/data.mdb | grep dev_scheduler_formats returns matches).
  2. 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.
  3. 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/4undef.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions