clockworklabs/SpacetimeDB · error

Invalid JSON5 string

Error message

Invalid JSON5 string

What it means

The environment config parser pre-scans quoted regions of a JSON5 file and decodes each quoted token with `json5::from_str`. If a quoted span is not a valid JSON5 string (e.g. an unterminated or malformed quote), parsing of the whole config fails with this generic error to avoid leaking secret-bearing diagnostics.

Solutions

  1. Check the config file for unterminated or improperly escaped quoted strings.
  2. Escape internal quotes (\" or \') and backslashes in values.
  3. Validate the JSON5 config with a local JSON5 parser before publishing.

Example fix

// before (config)
PASSWORD: 'it's secret
// after (config)
PASSWORD: "it's secret"
Defensive patterns

Strategy: validation

Validate before calling

// validate quotes in the config before running the CLI
const json5 = require('json5');
try { json5.parse(fs.readFileSync(configPath, 'utf8')); } catch (e) { console.error('Malformed quoted string in config:', e.message); }

Try / catch

match result { Err(e) if e.to_string() == "Invalid JSON5 string" => { eprintln!("Check unterminated quotes/escapes in config"); }, Err(e) => return Err(e), Ok(v) => v }

Prevention

When it happens

Trigger: A config file contains an unterminated or malformed quoted string, such as a missing closing quote or an invalid escape sequence inside a value.

Common situations: Hand-editing config with secrets; pasting values with unmatched quotes; apostrophes in passwords that clash with JSON5 single-quote delimiters.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


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

Appendix: source

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

                {
                    i += content[i..].chars().next().unwrap().len_utf8();
                }
                if i == start {
                    i += content[i..].chars().next().unwrap().len_utf8();
                }
                let token = &content[start..i];
                if (token.starts_with(|c: char| c.is_ascii_digit() || matches!(c, '-' | '+' | '.'))
                    || matches!(token, "Infinity" | "NaN"))
                    && !content[i..].trim_start_matches(is_space).starts_with(':')
                {
                    replacements.push((start, i));
                    numbers.push(token);
                    continue;
                }
            }
        }
        if matches!(bytes[start], b'\'' | b'"') {
            strings.push(json5::from_str(&content[start..i]).map_err(|_| anyhow::anyhow!("Invalid JSON5 string"))?);
        }
    }
    // Check decoded strings as well: Unicode escapes must not manufacture an
    // internal marker and cause a string to be interpreted as a number.
    let mut prefix = "__spacetime_numeric_".to_owned();
    while content.contains(&prefix) || strings.iter().any(|s| s.contains(&prefix)) {
        prefix.push('_');
    }
    let mut text = String::with_capacity(content.len());
    let mut previous = 0;
    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"))?;

View on GitHub (pinned to eddf9f5014)