clockworklabs/SpacetimeDB · error · NodesError
environment access requires a host-dispatched root module…
Error message
environment access requires a host-dispatched root module function
What it means
env_get reads configuration values from the st_env system table using the calling module's schema. To keep environment reads consistent and permissioned, SpacetimeDB only allows them from a host-dispatched, non-namespaced (root) module function — i.e. a real reducer, not an internal/inline or namespaced helper. Calling env_get outside that context raises this wrapped DBError.
Solutions
- Move the env_get call into a top-level reducer (a non-namespaced module function dispatched by the host).
- Pass the value obtained in the reducer as an argument to any namespaced helper that needs it.
- Validate the key first with spacetimedb_lib::environment::validate_key to avoid the separate InvalidEnvironmentKey error.
Example fix
// before: namespaced helper reads env itself
mod internal { pub fn read_key(env: &InstanceEnv, k: &str) { env.env_get(k); } }
// after: reducer reads env, passes value down
#[reducer]
fn apply_config(ctx: &ReducerContext) {
let v = ctx.env_get("API_KEY");
internal::apply(v);
} Defensive patterns
Strategy: validation
Validate before calling
// call env_get only from a root reducer context assert!(ctx.func_name().map_or(false, |n| !n.is_namespaced()), "env_get requires root reducer");
Try / catch
match env.env_get(key) { Err(NodesError::..) => log::error("env access outside reducer context"), other => other } Prevention
- Keep env_get calls inside top-level reducers
- Never call env APIs from namespaced helpers, views, or HTTP handlers
- Pass fetched values as parameters instead
When it happens
Trigger: Calling env_get (directly or via a read of the st_env table / config API) from a namespaced function (func name is_namespaced() is true), from no function context (func_name None), or when environment_call_active is false, e.g. from an HTTP/SQL code path rather than a dispatched reducer.
Common situations: Reading env config inside a scheduled procedure or namespaced module helper; attempting env access from a view or external API handler instead of a reducer; calling env_get before the host sets up the call context.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- {message}
- `emcc` not found in PATH. Activate Emscripten (emsdk_env).
- export is a function with incorrect type
- export is not a function
- export is a function with incorrect type
AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-09-20).
Data as JSON: /api/errors/4321063e03faf640.
Report an issue: GitHub.
Appendix: source
Thrown at crates/core/src/host/instance_env.rs:317
pub fn in_tx(&self) -> bool {
self.get_tx().is_ok()
}
pub(crate) fn take_tx(&self) -> Result<MutTxId, GetTxError> {
self.tx.take()
}
pub(crate) fn relational_db(&self) -> &Arc<RelationalDB> {
self.replica_ctx.relational_db()
}
/// Read configuration using the schema of this exact module instance.
/// Missing optional reads also register a view dependency on `st_env`.
pub(crate) fn env_get(&self, key: &str) -> Result<Option<String>, NodesError> {
use spacetimedb_datastore::system_tables::ST_ENV_ID;
spacetimedb_lib::environment::validate_key(key).map_err(|_| NodesError::InvalidEnvironmentKey)?;
if !self.environment_call_active || self.func_name.as_ref().is_none_or(|name| name.is_namespaced()) {
return Err(DBError::Other(anyhow::anyhow!(
"environment access requires a host-dispatched root module function"
))
.into());
}
if let Ok(mut tx) = self.get_tx() {
tx.record_table_scan(&self.func_type, ST_ENV_ID);
return self.read_declared_environment(&*tx, key);
}
if !matches!(self.func_type, FuncCallType::Procedure) {
return Err(NodesError::NotInTransaction);
}
self.relational_db()
.with_read_only(Workload::Internal, |tx| self.read_declared_environment(tx, key))
}
fn read_declared_environment(&self, state: &impl StateView, key: &str) -> Result<Option<String>, NodesError> {
use spacetimedb_datastore::system_tables::{StModuleFields, ST_MODULE_ID};
let fail = |message| NodesError::from(DBError::Other(anyhow::anyhow!("{message}")));View on GitHub (pinned to eddf9f5014)