{"record":{"id":"6f5d97569a532d66","repo":"zed-industries/zed","slug":"extension-has-invalid-zed-api-version-section","errorCode":null,"errorMessage":"extension {} has invalid zed:api-version section: {:?}","messagePattern":"extension (.+?) has invalid zed:api-version section: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/extension/src/extension.rs","lineNumber":201,"sourceCode":"    ) -> Result<Option<DebugScenario>>;\n    async fn run_dap_locator(\n        &self,\n        locator_name: String,\n        config: SpawnInTerminal,\n    ) -> Result<DebugRequest>;\n}\n\npub fn parse_wasm_extension_version(extension_id: &str, wasm_bytes: &[u8]) -> Result<Version> {\n    let mut version = None;\n\n    for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {\n        if let wasmparser::Payload::CustomSection(s) =\n            part.context(\"error parsing wasm extension\")?\n            && s.name() == \"zed:api-version\"\n        {\n            version = parse_wasm_extension_version_custom_section(s.data());\n            if version.is_none() {\n                bail!(\n                    \"extension {} has invalid zed:api-version section: {:?}\",\n                    extension_id,\n                    s.data()\n                );\n            }\n        }\n    }\n\n    // The reason we wait until we're done parsing all of the Wasm bytes to return the version\n    // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.\n    //\n    // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem\n    // earlier as an `Err` rather than as a panic.\n    version.with_context(|| format!(\"extension {extension_id} has no zed:api-version section\"))\n}\n\nfn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<Version> {\n    if data.len() == 6 {","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/zed-industries/zed/blob/5a9b9558db01a6b906cec2fb70a797affdc58cdd/crates/extension/src/extension.rs#L183-L219","documentation":"parse_wasm_extension_version() scans a compiled extension's .wasm for the 'zed:api-version' custom section, which Zed's extension builder writes as exactly 6 raw bytes: three big-endian u16s (major, minor, patch). parse_wasm_extension_version_custom_section() returns None unless data.len() == 6, so any other payload - a string like \"1.0.0\", an empty section, truncated bytes - triggers this bail. The section is Zed's embedded record of which Extension API version the wasm was built against.","triggerScenarios":"Loading or analyzing a Zed wasm extension whose zed:api-version custom section is not 6 bytes: wasm built by an outdated/foreign toolchain that wrote a different format (e.g. semver string or JSON), a hand-written build script that adds the custom section itself, or a corrupted/truncated .wasm file.","commonSituations":"Extension authors adding the section manually with a string version instead of binary u16 triples; wasm rebuilt or post-processed (wasm-opt/wasm-tools strip or rewrite custom sections); extensions compiled by mismatched Zed versions whose section format differed.","solutions":["Rebuild the extension with the current toolchain: 'zed extension build' (or crates/extension extension_builder), which writes the correct 6-byte section","If you emit the section yourself, write exactly 6 bytes: major, minor, patch as big-endian u16 each (e.g. version 0.6.0 -> [0x00,0x00, 0x00,0x06, 0x00,0x00])","Avoid wasm post-processing steps that drop custom sections (check wasm-opt/wasm-strip flags), or re-add the section afterwards","If the file was hand-modified or downloaded, recompile from source to eliminate corruption"],"exampleFix":"// before: writing the version as a string\nsection.data = b\"0.6.0\"; // len 5 -> 'invalid zed:api-version section'\n\n// after: exactly 6 bytes, three big-endian u16s\nlet (major, minor, patch) = (0u16, 6u16, 0u16);\nlet mut data = Vec::new();\nfor part in [major, minor, patch] {\n    data.extend_from_slice(&part.to_be_bytes());\n}\nsection.data = &data; // len 6 -> parses to Version::new(0, 6, 0)","handlingStrategy":"validation","validationCode":"// Verify the section length before handing the wasm to Zed\nfn has_valid_api_version(wasm_bytes: &[u8]) -> bool {\n    for part in wasmparser::Parser::new(0).parse_all(wasm_bytes).flatten() {\n        if let wasmparser::Payload::CustomSection(s) = part\n            && s.name() == \"zed:api-version\"\n        {\n            return s.data().len() == 6;\n        }\n    }\n    false\n}","typeGuard":null,"tryCatchPattern":"match parse_wasm_extension_version(extension_id, &wasm_bytes) {\n    Ok(version) => Some(version),\n    Err(err) if err.to_string().contains(\"invalid zed:api-version\") => {\n        log::warn!(\"skipping malformed extension {extension_id}: {err:#}\");\n        None\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Always build wasm through 'zed extension build' / extension_builder so the section format is correct","Never write the version as a string - it is three big-endian u16s, exactly 6 bytes","Skip wasm post-processors that strip custom sections, or re-emit the section afterwards"],"tags":["zed","extension","wasm","custom-section","semver","api-version"],"backgroundTag":"invalid-wasm-custom-section","analyzedSha":"5a9b9558db01a6b906cec2fb70a797affdc58cdd","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}