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
- Verify the key is a valid environment variable name (no control chars, non-empty) before calling env_get.
- Confirm the code runs inside a real SpacetimeDB host that supplies st_env, not a bare test harness.
- Check the errno value in the panic message against the Errno definitions to identify the host-side failure.
- 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
- Validate key names before calling env_get
- Only call env_get inside a real SpacetimeDB host runtime
- Map Errno values to actionable log messages in host tooling
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
- environment value does not match its declared enum
- required environment key is missing
- required environment key is missing
- a row was a sequence trigger but there was no generated…
- batch subscriptions without a module host are not supported…
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)