clockworklabs/SpacetimeDB · error

Invalid configuration structure

Error message

Invalid configuration structure

What it means

`decode_config` re-serializes the parsed JSON Value and deserializes it into `SpacetimeConfig`. If serialization of the Value itself fails (a degenerate/invalid Value), the error is this opaque message, again avoiding echoing config contents that may hold secrets.

Solutions

  1. Replace NaN/Infinity or other non-JSON values in the config with valid JSON numbers or strings.
  2. Simplify the config structure to plain JSON-compatible objects, arrays, strings, numbers, booleans.
  3. Update the CLI if this occurs with an apparently normal config, and report it as a bug.

Example fix

// before (config)
RATIO: NaN
// after (config)
RATIO: 0
Defensive patterns

Strategy: validation

Validate before calling

fn is_json_encodable(v: &serde_json::Value) -> bool { !matches!(v, serde_json::Value::Number(n) if n.as_f64().map_or(false, |f| f.is_nan() || f.is_infinite())) }

Try / catch

match result { Err(e) if e.to_string() == "Invalid configuration structure" => { eprintln!("Config Value is not JSON-encodable; remove NaN/Infinity or exotic values"); }, Err(e) => return Err(e), Ok(v) => v }

Prevention

When it happens

Trigger: A parsed Value that `serde_json::to_vec` cannot encode (e.g. non-string map keys or NaN/floating values not representable in JSON), passed through the environment config decode path.

Common situations: Config files with JSON5 extensions that produce Values outside strict JSON; exotic numeric inputs like NaN or Infinity at the top level of the config.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at crates/cli/src/spacetime_config/environment.rs:100

    for (index, (start, end)) in replacements.into_iter().enumerate() {
        text.push_str(&content[previous..start]);
        text.push_str(&format!("\"{prefix}{index}\""));
        previous = end;
    }
    text.push_str(&content[previous..]);
    // Parser diagnostics may quote the source line, which can contain secrets.
    let mut value: Value = json5::from_str(&text).map_err(|_| anyhow::anyhow!("Invalid JSON5 configuration"))?;
    restore(&mut value, &prefix, &numbers, None)?;
    Ok(value)
}

/// Deserialize through JSON text so Serde's flattened-field buffer does not
/// receive visit_u128 from Value's deserializer. That buffer cannot represent
/// u128, while the arbitrary-precision JSON parser preserves its decimal token.
/// Diagnostics discard values while retaining a bounded, ordinary unknown field
/// name, which is useful for correcting misspelled configuration options.
pub(super) fn decode_config(value: Value) -> anyhow::Result<super::SpacetimeConfig> {
    let encoded = serde_json::to_vec(&value).map_err(|_| anyhow::anyhow!("Invalid configuration structure"))?;
    serde_json::from_slice(&encoded).map_err(|error| {
        let diagnostic = error.to_string();
        if let Some((field, suffix)) = diagnostic
            .strip_prefix("unknown field `")
            .and_then(|message| message.split_once('`'))
            && suffix.starts_with(", expected ")
            && !field.is_empty()
            && field.len() <= 64
            && field
                .bytes()
                .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
        {
            return anyhow::anyhow!("unknown field `{field}`");
        }
        anyhow::anyhow!("Invalid configuration structure")
    })
}

View on GitHub (pinned to eddf9f5014)