{"record":{"id":"b1cfe3ded7f61e2b","repo":"openai/codex","slug":"expected-v2-namespace-in-bundle-definitions","errorCode":null,"errorMessage":"expected v2 namespace in bundle definitions","messagePattern":"expected v2 namespace in bundle definitions","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/app-server-protocol/src/export.rs","lineNumber":1100,"sourceCode":"/// 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();\n\n    for shared in FLAT_V2_SHARED_DEFINITIONS {\n        let Some(shared_schema) = definitions.get(*shared) else {\n            continue;\n        };\n        let shared_schema = shared_schema.clone();\n        non_v2_refs.extend(collect_non_v2_refs(&shared_schema));\n        shared_definitions.insert((*shared).to_string(), shared_schema);\n    }","sourceCodeStart":1082,"sourceCodeEnd":1118,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/app-server-protocol/src/export.rs#L1082-L1118","documentation":"Third guard in build_flat_v2_schema: it requires definitions[\"v2\"] to be an object, since its whole job is lifting the v2 namespace to the root definitions map for Python codegen. The merge step namespaces all v2 request, response, and notification schemas under definitions.v2, so a missing or non-object v2 means the bundle carries no v2 API surface at all.","triggerScenarios":"generate_json_with_experimental reaching the flattener with zero namespaced v2 schemas, for example when v2 emitters produced nothing or earlier retain filters dropped every v2 entry; or test bundles whose definitions map has no v2 key.","commonSituations":"Refactors that stop exporting v2 types or rename the namespace; fixtures built only from v1 types; experimental gating or allowlist filters that accidentally exclude all v2 emitters.","solutions":["Ensure at least one v2 schema lands under definitions.v2 before flattening: check the v2 emitters and the retain filters in generate_json_with_experimental.","In tests, include a non-empty v2 object in the fixture's definitions.","If it regressed outside tests, bisect changes to the export_client_* / export_server_* schema emitters and the namespace insertion logic."],"exampleFix":"// before\nlet bundle = json!({\"definitions\": {}}); // no v2 namespace: error\n\n// after\nlet bundle = json!({\"definitions\": {\n    \"v2\": {\"thread/start/params\": {\"type\": \"object\"}}\n}});","handlingStrategy":"type-guard","validationCode":"// Call-site check before flattening:\nif !has_v2_namespace(&bundle) {\n    return Err(anyhow!(\"bundle has no v2 namespace; check the v2 schema emitters\"));\n}","typeGuard":"fn has_v2_namespace(v: &serde_json::Value) -> bool {\n    v.get(\"definitions\")\n        .and_then(|d| d.get(\"v2\"))\n        .is_some_and(serde_json::Value::is_object)\n}","tryCatchPattern":null,"preventionTips":["Assert the v2 namespace is non-empty in export tests so silent emitter loss fails early.","When gating or filtering schemas, verify at least one v2 emitter survives the filter.","Treat this guard as a pipeline invariant: fix the emitters, never bypass the check."],"tags":["json-schema","codegen","v2-namespace","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"}