The root cause: modules run hardcoded start as the entrypoint and, when a module didn't export it, surfaced a bare wasm: function start not found with no way to discover the real exports. (Reproduced: mod.ExportedFunction("start") just returns nil — the CLI turned that into a hint-less error.)
All green. Writing the final report.
The root cause: modules run hardcoded _start as the entrypoint and, when a module didn't export it, surfaced a bare wasm: function _start not found with no way to discover the real exports. (Reproduced: mod.ExportedFunction("_start") just returns nil — the CLI turned that into a hint-less error.)
pkg/wasm/runtime.go — actionable lookup + ExportNames helper// ExportNames returns the sorted names of every function exported by mod.
func (r *Runtime) ExportNames(mod api.Module) []string {
defs := mod.ExportedFunctionDefinitions()
names := make([]string, 0, len(defs))
for name := range defs {
names = append(names, name)
}
sort.Strings(names)
return names
}
// LookupFunction returns the exported function fn, or an error that lists the
// sorted available exports and hints at the --fn flag.
func (r *Runtime) LookupFunction(mod api.Module, fn string) (api.Function, error) {
if f := mod.ExportedFunction(fn); f != nil {
return f, nil
}
return nil, fmt.Errorf(
"wasm: function %q not found; available exports: %s (pass --fn to select one)",
fn, strings.Join(r.ExportNames(mod), ", "))
}
Also added InstantiateFile, which disables wazero's implicit _start start-function (WithStartFunctions()) and wires WASI stdout, so the command runs exactly the export it resolved.
internal/builtin/modules.go — auto-select vs. strict// Without --fn, auto-select the module's sole exported function instead of
// blindly attempting "_start" (which previously failed cryptically).
if !cmd.Flags().Changed(fnFlag) {
exports := rt.ExportNames(mod)
switch len(exports) {
case 0:
return errors.New("module has no exported functions")
case 1:
fn = exports[0]
default:
return fmt.Errorf("module exports %d functions; pass --fn to select one: %s",
len(exports), strings.Join(exports, ", "))
}
}
// With an explicit --fn, be strict: a missing export fails with a hint
// listing the sorted available exports.
f, err := rt.LookupFunction(mod, fn)
cmd.Flags().Changed("fn") is the discriminator: unset → lenient sole-export auto-select; set → strict lookup with the hint-listing error.
Verified in a fresh module at ~/openapi-cli (Go 1.26, wazero v1.12.0, cobra). Fixtures are hand-written WAT compiled via wat2wasm: hello.wasm (WASI fd_write, sole fn export hello), nostart.wasm (no _start, sole export answer → 42), multi.wasm (exports alpha, beta).
go test ./... -race -count=1 — 10/10 passing, gofmt and go vet clean. Regression assertions:
TestLookupFunctionMissingListsExportsAndFnHint — LookupFunction(mod, "_start") error contains "wasm: function \"_start\" not found", available exports: hello, and --fn.TestLookupFunctionMissingSortedMultiple — missing fn on multi-export module lists alpha, beta (sorted) + --fn.TestRunNoFlagAutoSelectsSoleExport — no-flag run executes hello (stdout captured: hello).TestRunExplicitFnMissingErrorsWithHint — --fn _start errors with available exports + --fn.TestRunNoFlagMultipleExportsRequiresFn / TestRunExplicitFnRuns — multi-export without --fn refuses; explicit --fn beta runs strictly.End-to-end CLI runs:
$ openapi-cli modules run hello.wasm # no --fn
hello
[0]
$ openapi-cli modules run nostart.wasm # no _start, sole export
[42]
$ openapi-cli modules run hello.wasm --fn _start # strict, missing
Error: wasm: function "_start" not found; available exports: hello (pass --fn to select one)
$ openapi-cli modules run multi.wasm # no --fn, ambiguous
Error: module exports 2 functions; pass --fn to select one: alpha, beta
$ openapi-cli modules run multi.wasm --fn beta # strict, found
[2]
Edge cases covered: sole-export auto-select (WASI + non-WASI), no _start at all, multiple exports without --fn, explicit --fn missing vs. present, sorted export listing, WASI stdout capture, race-clean.
{"problem_class":"go-wasm-modules-run-export-hint","model":"deepseek-v4-flash","result":"passed","tests":10}
Solved by Pi Agent (deepseek-v4-flash).
All green. Writing the final report.
The root cause: modules run hardcoded _start as the entrypoint and, when a module didn't export it, surfaced a bare wasm: function _start not found with no way to discover the real exports. (Reproduced: mod.ExportedFunction("_start") just returns nil — the CLI turned that into a hint-less error.)
pkg/wasm/runtime.go — actionable lookup + ExportNames helper// ExportNames returns the sorted names of every function exported by mod.
func (r *Runtime) ExportNames(mod api.Module) []string {
defs := mod.ExportedFunctionDefinitions()
names := make([]string, 0, len(defs))
for name := range defs {
names = append(names, name)
}
sort.Strings(names)
return names
}
// LookupFunction returns the exported function fn, or an error that lists the
// sorted available exports and hints at the --fn flag.
func (r *Runtime) LookupFunction(mod api.Module, fn string) (api.Function, error) {
if f := mod.ExportedFunction(fn); f != nil {
return f, nil
}
return nil, fmt.Errorf(
"wasm: function %q not found; available exports: %s (pass --fn to select one)",
fn, strings.Join(r.ExportNames(mod), ", "))
}
Also added InstantiateFile, which disables wazero's implicit _start start-function (WithStartFunctions()) and wires WASI stdout, so the command runs exactly the export it resolved.
internal/builtin/modules.go — auto-select vs. strict// Without --fn, auto-select the module's sole exported function instead of
// blindly attempting "_start" (which previously failed cryptically).
if !cmd.Flags().Changed(fnFlag) {
exports := rt.ExportNames(mod)
switch len(exports) {
case 0:
return errors.New("module has no exported functions")
case 1:
fn = exports[0]
default:
return fmt.Errorf("module exports %d functions; pass --fn to select one: %s",
len(exports), strings.Join(exports, ", "))
}
}
// With an explicit --fn, be strict: a missing export fails with a hint
// listing the sorted available exports.
f, err := rt.LookupFunction(mod, fn)
cmd.Flags().Changed("fn") is the discriminator: unset → lenient sole-export auto-select; set → strict lookup with the hint-listing error.
Verified in a fresh module at ~/openapi-cli (Go 1.26, wazero v1.12.0, cobra). Fixtures are hand-written WAT compiled via wat2wasm: hello.wasm (WASI fd_write, sole fn export hello), nostart.wasm (no _start, sole export answer → 42), multi.wasm (exports alpha, beta).
go test ./... -race -count=1 — 10/10 passing, gofmt and go vet clean. Regression assertions:
TestLookupFunctionMissingListsExportsAndFnHint — LookupFunction(mod, "_start") error contains "wasm: function \"_start\" not found", available exports: hello, and --fn.TestLookupFunctionMissingSortedMultiple — missing fn on multi-export module lists alpha, beta (sorted) + --fn.TestRunNoFlagAutoSelectsSoleExport — no-flag run executes hello (stdout captured: hello).TestRunExplicitFnMissingErrorsWithHint — --fn _start errors with available exports + --fn.TestRunNoFlagMultipleExportsRequiresFn / TestRunExplicitFnRuns — multi-export without --fn refuses; explicit --fn beta runs strictly.End-to-end CLI runs:
$ openapi-cli modules run hello.wasm # no --fn
hello
[0]
$ openapi-cli modules run nostart.wasm # no _start, sole export
[42]
$ openapi-cli modules run hello.wasm --fn _start # strict, missing
Error: wasm: function "_start" not found; available exports: hello (pass --fn to select one)
$ openapi-cli modules run multi.wasm # no --fn, ambiguous
Error: module exports 2 functions; pass --fn to select one: alpha, beta
$ openapi-cli modules run multi.wasm --fn beta # strict, found
[2]
Edge cases covered: sole-export auto-select (WASI + non-WASI), no _start at all, multiple exports without --fn, explicit --fn missing vs. present, sorted export listing, WASI stdout capture, race-clean.
{"problem_class":"go-wasm-modules-run-export-hint","model":"deepseek-v4-flash","result":"passed","tests":10}
Solved by Pi Agent (deepseek-v4-flash).