{"record":{"id":"64606335daf403cc","repo":"openai/codex","slug":"expected-bundle-root-to-be-an-object","errorCode":null,"errorMessage":"expected bundle root to be an object","messagePattern":"expected bundle root to be an object","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/app-server-protocol/src/export.rs","lineNumber":1091,"sourceCode":"\n    Ok(Value::Object(root))\n}\n\n/// Build a datamodel-code-generator-friendly v2 bundle from the mixed export.\n///\n/// The full bundle keeps v2 schemas nested under `definitions.v2`, plus a few\n/// shared root definitions like `ClientRequest` and `ServerNotification`.\n/// Python codegen only walks one definitions map level, so\n/// a direct feed would treat `v2` itself as a schema and miss unreferenced v2\n/// leaves. This helper flattens all v2 definitions to the root definitions map,\n/// then pulls in the shared root schemas and any non-v2 transitive deps they\n/// still reference. Keep the shared root unions intact here: some valid\n/// request/notification/event variants are inline or only reference shared root\n/// helpers, so filtering them by the presence of a `#/definitions/v2/` ref\n/// would silently drop real API surface from the flat bundle.\nfn build_flat_v2_schema(bundle: &Value) -> Result<Value> {\n    let Value::Object(root) = bundle else {\n        return Err(anyhow!(\"expected bundle root to be an object\"));\n    };\n    let definitions = root\n        .get(\"definitions\")\n        .and_then(Value::as_object)\n        .ok_or_else(|| anyhow!(\"expected bundle definitions map\"))?;\n    let v2_definitions = definitions\n        .get(\"v2\")\n        .and_then(Value::as_object)\n        .ok_or_else(|| anyhow!(\"expected v2 namespace in bundle definitions\"))?;\n\n    let mut flat_root = root.clone();\n    let title = root\n        .get(\"title\")\n        .and_then(Value::as_str)\n        .unwrap_or(\"CodexAppServerProtocol\");\n    let mut flat_definitions = v2_definitions.clone();\n    let mut shared_definitions = Map::new();\n    let mut non_v2_refs = HashSet::new();","sourceCodeStart":1073,"sourceCodeEnd":1109,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/app-server-protocol/src/export.rs#L1073-L1109","documentation":"build_flat_v2_schema flattens the merged JSON Schema bundle for datamodel-code-generator, which only walks one level of definitions. Its first guard requires the bundle root to be a JSON object. The production merge step always returns an object with $schema, title, type, and definitions, so this error means the function received something that step would not produce: a hand-built fixture, a raw sub-schema, or a transformed Value.","triggerScenarios":"Calling build_flat_v2_schema with a Value whose root is an array, string, number, or null: unit-test fixtures crafted with json!, or a refactor that feeds a schemars sub-schema or a definitions array instead of the merged bundle object.","commonSituations":"Writing tests for the flat-v2 flattening with minimal ad-hoc inputs; refactors of generate_json_with_experimental that change what is passed to the flattener.","solutions":["Pass a bundle shaped like the merge step output: an object with $schema, title, type object, and definitions keys.","In tests, build the fixture through the real merge path or at minimum wrap it as json!({\"definitions\": {...}}).","If it fires in a production path, the upstream invariant at the merge step broke; diff recent changes around the bundle construction."],"exampleFix":"// before\nlet flat = build_flat_v2_schema(&json!([{\"definitions\": {}}]))?; // array root: error\n\n// after\nlet flat = build_flat_v2_schema(&json!({\n    \"definitions\": {\"v2\": {}}\n}))?;","handlingStrategy":"type-guard","validationCode":"// Call-site check before flattening:\nif !is_flat_bundle_input(&bundle) {\n    return Err(anyhow!(\"bundle producer must emit an object with a definitions map\"));\n}","typeGuard":"fn is_flat_bundle_input(v: &serde_json::Value) -> bool {\n    v.as_object().is_some_and(|root| {\n        root.get(\"definitions\").is_some_and(serde_json::Value::is_object)\n    })\n}","tryCatchPattern":null,"preventionTips":["Generate test fixtures through the real merge step instead of hand-written Values.","Keep the bundle contract (object root with a definitions map) documented at the producer.","Treat these guards as fail-fast checks: fix the producer rather than catching the error."],"tags":["json-schema","codegen","invariant","export","schema-bundle"],"backgroundTag":"schema-validation-failed","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}