{"record":{"id":"99c6c482b5d4add9","repo":"neondatabase/neon","slug":"could-not-parse-config-file","errorCode":null,"errorMessage":"could not parse config file: {}","messagePattern":"could not parse config file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compute_tools/src/configurator.rs","lineNumber":81,"sourceCode":"            // Drop the lock guard here to avoid holding the lock while downloading config from the control plane / HCC.\n            // This is the only thread that can move compute_ctl out of the `RefreshConfiguration` state, so it\n            // is safe to drop the lock like this.\n            drop(state);\n\n            let get_config_result: anyhow::Result<ComputeConfig> =\n                if let Some(config_path) = &compute.params.config_path_test_only {\n                    // This path is only to make testing easier. In production we always get the config from the HCC.\n                    info!(\n                        \"reloading config.json from path: {}\",\n                        config_path.to_string_lossy()\n                    );\n                    let path = Path::new(config_path);\n                    if let Ok(file) = File::open(path) {\n                        match serde_json::from_reader::<File, ComputeConfig>(file) {\n                            Ok(config) => Ok(config),\n                            Err(e) => {\n                                error!(\"could not parse config file: {}\", e);\n                                Err(anyhow::anyhow!(\"could not parse config file: {}\", e))\n                            }\n                        }\n                    } else {\n                        error!(\n                            \"could not open config file at path: {:?}\",\n                            config_path.to_string_lossy()\n                        );\n                        Err(anyhow::anyhow!(\n                            \"could not open config file at path: {}\",\n                            config_path.to_string_lossy()\n                        ))\n                    }\n                } else if let Some(control_plane_uri) = &compute.params.control_plane_uri {\n                    get_config_from_control_plane(control_plane_uri, &compute.params.compute_id)\n                } else {\n                    Err(anyhow::anyhow!(\"config_path_test_only is not set\"))\n                };\n","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/compute_tools/src/configurator.rs#L63-L99","documentation":"On the test-only configuration path, compute_ctl tried to serde-parse the file at params.config_path_test_only as a ComputeConfig JSON document and serde failed. The error chain includes the serde message with line/column. Production always takes the control-plane path instead; this fires only when config_path_test_only is explicitly set.","triggerScenarios":"configurator runs with --config-path-test-only (or equivalent param) pointing at a file that is valid enough to open but is not parseable as ComputeConfig: malformed JSON, wrong field types, unknown/renamed required fields.","commonSituations":"Hand-edited config.json with trailing commas or comments (not valid JSON); schema drift after upgrading compute_ctl (fields renamed/typed differently); pointing the flag at a ComputeSpec file instead of a ComputeConfig wrapper; file truncated mid-write.","solutions":["Validate the file is well-formed JSON first (jq . config.json)","Diff the document against the ComputeConfig serde schema of the compute_ctl build you run (fields, nesting, types)","Regenerate the config file from a known-good source or copy an example from the repo's test fixtures","Keep humans out of the loop: write the file atomically from tooling to avoid truncation"],"exampleFix":"// before\nlet path = Path::new(config_path);\nif let Ok(file) = File::open(path) { serde_json::from_reader::<File, ComputeConfig>(file) ... }\n// after: surface serde position instead of a generic re-wrap\nmatch serde_json::from_reader::<File, ComputeConfig>(file) {\n    Ok(config) => Ok(config),\n    Err(e) => Err(anyhow!(\"could not parse config file at {}: {e}\", config_path.display())),\n}","handlingStrategy":"validation","validationCode":"// Validate before compute_ctl reads it\nlet raw = std::fs::read_to_string(path)?;\nserde_json::from_str::<ComputeConfig>(&raw)\n    .map_err(|e| anyhow!(\"config at {path} is not a valid ComputeConfig: {e}\"))?;","typeGuard":"fn is_parseable_compute_config(path: &Path) -> bool {\n    std::fs::File::open(path).ok()\n        .and_then(|f| serde_json::from_reader::<_, ComputeConfig>(f).ok())\n        .is_some()\n}","tryCatchPattern":null,"preventionTips":["Lint config files with a JSON schema/serde check in CI before deployment","Generate configs programmatically and write atomically (tmp + rename) to avoid truncated files","On compute_ctl upgrades, re-validate existing config fixtures against the new schema"],"tags":["rust","compute-ctl","config","json","serde","test-only"],"backgroundTag":"config-parse-error","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}