{"record":{"id":"5fa763e91070c6d1","repo":"clockworklabs/SpacetimeDB","slug":"call-procedure-dunder-export-is-not-a-function","errorCode":null,"errorMessage":"{CALL_PROCEDURE_DUNDER} export is not a function","messagePattern":"(.+?) export is not a function","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/host/wasmtime/wasmtime_module.rs","lineNumber":455,"sourceCode":"/// which is fine because they also won't define any procedures.\n///\n/// Panics if the `instance` has an export at the expected name,\n/// but it is not a function or is a function of an inappropriate type.\n/// For new modules, this will be caught during publish.\n/// Old modules from before the introduction of procedures might have an export at that name,\n/// but it follows the double-underscore pattern of reserved names,\n/// so we're fine to break those modules.\nfn get_call_procedure(store: &mut Store<WasmInstanceEnv>, instance: &Instance) -> Option<CallProcedureType> {\n    // Wasmtime uses `anyhow` for error reporting, vexing library users the world over.\n    // This means we can't distinguish between the failure modes of `Instance::get_typed_func`.\n    // Instead, we type out the body of that method ourselves,\n    // but with error handling appropriate to our needs.\n    let export = instance.get_export(store.as_context_mut(), CALL_PROCEDURE_DUNDER)?;\n\n    Some(\n        export\n            .into_func()\n            .unwrap_or_else(|| panic!(\"{CALL_PROCEDURE_DUNDER} export is not a function\"))\n            .typed(store)\n            .unwrap_or_else(|err| panic!(\"{CALL_PROCEDURE_DUNDER} export is a function with incorrect type: {err}\")),\n    )\n}\n\n/// Look up the `instance`'s export named by [`CALL_VIEW_DUNDER`].\n///\n/// Similar to [`get_call_procedure`], but for views.\nfn get_call_view(store: &mut Store<WasmInstanceEnv>, instance: &Instance) -> Option<CallViewType> {\n    let export = instance.get_export(store.as_context_mut(), CALL_VIEW_DUNDER)?;\n    Some(\n        export\n            .into_func()\n            .unwrap_or_else(|| panic!(\"{CALL_VIEW_DUNDER} export is not a function\"))\n            .typed(store)\n            .unwrap_or_else(|err| panic!(\"{CALL_VIEW_DUNDER} export is a function with incorrect type: {err}\")),\n    )\n}","sourceCodeStart":437,"sourceCodeEnd":473,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/6dee26c6efc2856793e12b148a59742964f5d783/crates/core/src/host/wasmtime/wasmtime_module.rs#L437-L473","documentation":"Host-side panic while instantiating a wasm module: the module exports something named `__call_procedure__` but the export is not a function (it is a memory, table, or global). The SpacetimeDB ABI reserves double-underscore names; `get_call_procedure` looks the export up and requires a function of the exact expected signature. Missing exports are fine (pre-procedure modules), but a wrong-kind export corrupts the ABI, so instantiation panics. New modules get this validated at publish time; old or hand-built modules can slip through.","triggerScenarios":"Publishing/wasm-instantiating a module where `__call_procedure__` was exported as a global/table/memory: hand-written WAT/wasm, custom build pipelines that emit extra exports, name collisions from obfuscators or linkers, or modules built with mismatched toolchain versions.","commonSituations":"Building modules outside the official SDK (raw cargo/rustc targets without spacetimedb bindings); wasm post-processing tools (optimizers, symbol renaming) turning the export into a different entity; upgrading spacetimedb where new dunder exports were introduced while an old artifact is re-uploaded.","solutions":["Rebuild the module with the official SpacetimeDB SDK/CLI (`spacetime publish`) so the dunder exports are generated correctly.","Inspect the artifact before publishing: `wasm-objdump -x module.wasm | grep -A2 __call_procedure__` (or `wasm-tools print`) and confirm the Export is a `func`.","Remove any custom exports/renames that collide with reserved double-underscore names in your build pipeline.","If the module was built by the SDK and still misbehaves, report the toolchain versions with the wasm export dump."],"exampleFix":"; before (WAT): export is not a function\n(global (export \"__call_procedure__\") i32 (i32.const 0))\n\n; after\n(func (export \"__call_procedure__\") (param i32) (result i32)\n  ;; dispatch to procedures as generated by the SDK\n)","handlingStrategy":"type-guard","validationCode":"# Inspect the artifact before publishing:\nwasm-objdump -x module.wasm | grep -A1 '__call_procedure__'\n# The Export must show type: func (not global/table/memory).","typeGuard":"// Host-side narrowing: only treat dunder exports as callables when the\n// export kind is Func, and skip/error otherwise instead of panicking.\nfn get_call_procedure_safe(\n    store: &mut Store<WasmInstanceEnv>,\n    instance: &Instance,\n) -> Option<CallProcedureType> {\n    let export = instance.get_export(store.as_context_mut(), CALL_PROCEDURE_DUNDER)?;\n    let func = export.into_func()?; // None instead of panic for non-func exports\n    func.typed(store).ok()\n}","tryCatchPattern":null,"preventionTips":["Always build modules through the SpacetimeDB SDK/CLI so dunder exports are emitted correctly.","Add a CI step that fails when reserved `__call_*__` exports exist with non-func kinds.","Avoid wasm post-processing (renaming/optimization) that can change export kinds."],"tags":["spacetimedb","wasm","wasmtime","export-mismatch","module-abi"],"backgroundTag":"wasm-export-type-mismatch","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}