neondatabase/neon · critical

could not parse spec

Error message

could not parse spec

What it means

A ComputeSpec arrived and ParsedSpec::try_from(spec) failed, so the configurator replaces it with the generic 'could not parse spec'. The real cause is one of the required-field/parse errors inside try_from (missing pageserver connstring, safekeepers, tenant/timeline id, or a malformed UUID). Note the code uses 'if let Ok', which discards the underlying anyhow error - the single biggest debugging obstacle here.

Source

Thrown at compute_tools/src/configurator.rs:107

                        Err(anyhow::anyhow!(
                            "could not open config file at path: {}",
                            config_path.to_string_lossy()
                        ))
                    }
                } else if let Some(control_plane_uri) = &compute.params.control_plane_uri {
                    get_config_from_control_plane(control_plane_uri, &compute.params.compute_id)
                } else {
                    Err(anyhow::anyhow!("config_path_test_only is not set"))
                };

            // Parse any received ComputeSpec and transpose the result into a Result<Option<ParsedSpec>>.
            let parsed_spec_result: Result<Option<ParsedSpec>> =
                get_config_result.and_then(|config| {
                    if let Some(spec) = config.spec {
                        if let Ok(pspec) = ParsedSpec::try_from(spec) {
                            Ok(Some(pspec))
                        } else {
                            Err(anyhow::anyhow!("could not parse spec"))
                        }
                    } else {
                        Ok(None)
                    }
                });

            let new_status: ComputeStatus;
            match parsed_spec_result {
                // Control plane (HCM) returned a spec and we were able to parse it.
                Ok(Some(pspec)) => {
                    {
                        let mut state = compute.state.lock().unwrap();
                        // Defensive programming to make sure this thread is indeed the only one that can move the compute
                        // node out of the `RefreshConfiguration` state. Would be nice if we can encode this invariant
                        // into the type system.
                        assert_eq!(state.status, ComputeStatus::RefreshConfiguration);

                        if state

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Propagate the real error: replace 'if let Ok(pspec)' with a match that chains {:#} of the underlying error - the message then tells you which field is the culprit
  2. Independently run the spec through ParsedSpec::try_from (unit test / small binary) to see the actual failure
  3. Fix the spec per the revealed message: add the missing GUCs/fields or correct malformed UUIDs
  4. Check for control-plane vs compute_ctl version skew and align them

Example fix

// before
if let Ok(pspec) = ParsedSpec::try_from(spec) { Ok(Some(pspec)) } else { Err(anyhow!("could not parse spec")) }
// after
match ParsedSpec::try_from(spec) {
    Ok(pspec) => Ok(Some(pspec)),
    Err(e) => Err(anyhow!("could not parse spec: {e:#}")),
}
Defensive patterns

Strategy: validation

Validate before calling

// Reuse the parser as a validator: run try_from and keep the real error
let probe = ParsedSpec::try_from(spec.clone());
if let Err(e) = probe { anyhow::bail!("spec will fail to parse: {e:#}"); }

Type guard

fn is_parseable_spec(spec: &ComputeSpec) -> bool {
    ParsedSpec::try_from(spec.clone()).is_ok()
}

Try / catch

// Propagate the source error chain instead of the generic message
match ParsedSpec::try_from(spec) {
    Ok(pspec) => Ok(Some(pspec)),
    Err(e) => Err(anyhow!("could not parse spec: {e:#}")),
}

Prevention

When it happens

Trigger: get_config_result carries a config whose spec fails ParsedSpec::try_from: any of errors 3-6 (missing neon.pageserver_connstring / neon.safekeepers / neon.tenant_id / neon.timeline_id) or 'invalid tenant id' / 'invalid timeline id' formatting, or later try_from invariants.

Common situations: Control plane and compute_ctl version skew producing incomplete specs; new validation added to try_from rejecting previously-accepted specs; hand-crafted specs for local testing; JSON field renames between versions.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/44c5038bafe622c2. Report an issue: GitHub.