clockworklabs/SpacetimeDB · error

Error reading environment

Error message

Error reading environment: {errno}

What it means

env_get reads a database environment variable via the FFI binding raw::env_get without exposing the system table. If the underlying host call returns a non-zero errno, the function panics with 'Error reading environment: {errno}' instead of returning an Option. This is an unrecoverable host/FFI failure, distinct from the key simply being absent (which yields None).

Solutions

  1. Verify the key is a valid environment variable name (no control chars, non-empty) before calling env_get.
  2. Confirm the code runs inside a real SpacetimeDB host that supplies st_env, not a bare test harness.
  3. Check the errno value in the panic message against the Errno definitions to identify the host-side failure.
  4. If the key may be legitimately absent, rely on the None return instead of treating absence as an FFI error.

Example fix

// before
let v = env_get("PORT").unwrap_or_default();
// after
let v = match std::panic::catch_unwind(|| env_get("PORT")) {
    Ok(Some(src)) => src,
    _ => Default::default(),
};
Defensive patterns

Strategy: try-catch

Validate before calling

fn valid_env_key(k: &str) -> bool { !k.is_empty() && k.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_') }

Type guard

fn checked_env_get(k: &str) -> Option<raw::BytesSource> { assert!(valid_env_key(k)); std::panic::catch_unwind(|| env_get(k)).ok().flatten() }

Try / catch

let value = std::panic::catch_unwind(|| env_get(key)).ok().flatten();

Prevention

When it happens

Trigger: Calling spacetimedb::env_get(key) when the FFI call raw::env_get returns an Errno (e.g. malformed key pointer/length, host not providing the env table, or host-side internal failure).

Common situations: Calling env_get inside a reducer against a host runtime that lacks an environment table, or with a key that the host rejects at the FFI boundary; also seen when running module code outside a proper host (tests, mocked runtimes).

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages 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/2fdd80755ab3aac7. Report an issue: GitHub.

Appendix: source

Thrown at crates/bindings-sys/src/lib.rs:1511

#[inline]
pub fn get_jwt(connection_id: [u8; 16]) -> Option<raw::BytesSource> {
    let source = unsafe {
        call(|out| raw::get_jwt(connection_id.as_ptr(), out))
            .unwrap_or_else(|errno: Errno| panic!("Error getting jwt: {errno}"))
    };

    if source == raw::BytesSource::INVALID {
        None // No JWT found.
    } else {
        Some(source)
    }
}

/// Read a database environment value without exposing the system table.
#[inline]
pub fn env_get(key: &str) -> Option<raw::BytesSource> {
    let source = unsafe { call(|out| raw::env_get(key.as_ptr(), key.len(), out)) }
        .unwrap_or_else(|errno: Errno| panic!("Error reading environment: {errno}"));
    (source != raw::BytesSource::INVALID).then_some(source)
}

pub struct RowIter {
    raw: raw::RowIter,
}

impl RowIter {
    /// Read some number of BSATN-encoded rows into the provided buffer.
    ///
    /// Returns the number of new bytes added to the end of the buffer.
    /// When the iterator has been exhausted,
    /// `self.is_exhausted()` will return `true`.
    pub fn read(&mut self, buf: &mut Vec<u8>) -> usize {
        loop {
            let buf_ptr = buf.spare_capacity_mut();
            let mut buf_len = buf_ptr.len();
            let ret = unsafe { raw::row_iter_bsatn_advance(self.raw, buf_ptr.as_mut_ptr().cast(), &mut buf_len) };

View on GitHub (pinned to eddf9f5014)