{"record":{"id":"4321063e03faf640","repo":"clockworklabs/SpacetimeDB","slug":"environment-access-requires-a-host-dispatched-root-module","errorCode":null,"errorMessage":"environment access requires a host-dispatched root module function","messagePattern":"environment access requires a host-dispatched root module function","errorType":"exception","errorClass":"NodesError","httpStatus":null,"severity":"error","filePath":"crates/core/src/host/instance_env.rs","lineNumber":317,"sourceCode":"    pub fn in_tx(&self) -> bool {\n        self.get_tx().is_ok()\n    }\n\n    pub(crate) fn take_tx(&self) -> Result<MutTxId, GetTxError> {\n        self.tx.take()\n    }\n\n    pub(crate) fn relational_db(&self) -> &Arc<RelationalDB> {\n        self.replica_ctx.relational_db()\n    }\n\n    /// Read configuration using the schema of this exact module instance.\n    /// Missing optional reads also register a view dependency on `st_env`.\n    pub(crate) fn env_get(&self, key: &str) -> Result<Option<String>, NodesError> {\n        use spacetimedb_datastore::system_tables::ST_ENV_ID;\n        spacetimedb_lib::environment::validate_key(key).map_err(|_| NodesError::InvalidEnvironmentKey)?;\n        if !self.environment_call_active || self.func_name.as_ref().is_none_or(|name| name.is_namespaced()) {\n            return Err(DBError::Other(anyhow::anyhow!(\n                \"environment access requires a host-dispatched root module function\"\n            ))\n            .into());\n        }\n        if let Ok(mut tx) = self.get_tx() {\n            tx.record_table_scan(&self.func_type, ST_ENV_ID);\n            return self.read_declared_environment(&*tx, key);\n        }\n        if !matches!(self.func_type, FuncCallType::Procedure) {\n            return Err(NodesError::NotInTransaction);\n        }\n        self.relational_db()\n            .with_read_only(Workload::Internal, |tx| self.read_declared_environment(tx, key))\n    }\n\n    fn read_declared_environment(&self, state: &impl StateView, key: &str) -> Result<Option<String>, NodesError> {\n        use spacetimedb_datastore::system_tables::{StModuleFields, ST_MODULE_ID};\n        let fail = |message| NodesError::from(DBError::Other(anyhow::anyhow!(\"{message}\")));","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/eddf9f5014579a50d4b67630e28b6e15cad9c4af/crates/core/src/host/instance_env.rs#L299-L335","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: namespaced helper reads env itself\nmod internal { pub fn read_key(env: &InstanceEnv, k: &str) { env.env_get(k); } }\n\n// after: reducer reads env, passes value down\n#[reducer]\nfn apply_config(ctx: &ReducerContext) {\n    let v = ctx.env_get(\"API_KEY\");\n    internal::apply(v);\n}","handlingStrategy":"validation","validationCode":"// call env_get only from a root reducer context\nassert!(ctx.func_name().map_or(false, |n| !n.is_namespaced()), \"env_get requires root reducer\");","typeGuard":null,"tryCatchPattern":"match env.env_get(key) { Err(NodesError::..) => log::error(\"env access outside reducer context\"), other => other }","preventionTips":["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"],"tags":["wasm","module","environment","invalid-call-context"],"backgroundTag":"unsupported-operation","analyzedSha":"eddf9f5014579a50d4b67630e28b6e15cad9c4af","analyzedAt":"2026-09-20T12:15:59.611Z","contentChangedAt":"2026-09-20T12:15:59.611Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}