clockworklabs/SpacetimeDB · error · NodesError

{message}

Error message

{message}

What it means

read_declared_environment resolves a configuration key against the environment schema declared by the current environment module. It builds failures as NodesError::from(DBError::Other(anyhow!(message))) so the message text itself is the error. It fails when no environment module is attached, the key has no declaration, or the underlying database read fails.

Solutions

  1. Publish the database with an environment so the environment module/schema is attached.
  2. Check the exact key spelling against the declared environment; add the key via a publish.
  3. If 'environment schema is not available', wait for module initialization to complete and retry.

Example fix

// before: reading undeclared key
let v = env_get("APY_KEY");

// after: declared key published via module
let v = env_get("API_KEY").expect("API_KEY declared at publish time");
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure key declared before reading
let declared = module.environment().map(|e| e.contains_key(key)).unwrap_or(false);

Try / catch

match read_declared_environment(tx, key) { Ok(v) => v, Err(e) => { log::warn!("env read failed: {e}"); None } }

Prevention

When it happens

Trigger: Called by env_get after the transactional path fails: the instance has no environment_module (module not published with an environment), the requested key is absent from the module's declared environment, or the st_env read in a read-only transaction returns an error.

Common situations: Querying a key that was never declared via publishing; reading env values before the environment schema is available (module still initializing); typos in the key name.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-09-20). Data as JSON: /api/errors/421508a71c6e4b78. Report an issue: GitHub.

Appendix: source

Thrown at crates/core/src/host/instance_env.rs:335

            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}")));
        let (hash, module) = self
            .environment_module
            .as_ref()
            .ok_or_else(|| fail("environment schema is not available"))?;
        let declaration = module
            .environment()
            .get(key)
            .ok_or_else(|| fail("environment key is not declared"))?;
        // Check inside this same snapshot. A suspended old procedure must never
        // combine its declarations with values installed for a different module.
        let row = state
            .iter(ST_MODULE_ID)
            .map_err(DBError::from)?
            .next()
            .ok_or_else(|| fail("database program is not initialized"))?;
        let current_hash = spacetimedb_datastore::system_tables::read_hash_from_col(row, StModuleFields::ProgramHash)
            .map_err(DBError::from)?;
        if current_hash != *hash {

View on GitHub (pinned to eddf9f5014)