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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions