zed-industries/zed · error · anyhow::Error
Unsupported $ref format (only `#/$defs/<name>` and `#/defini
Error message
Unsupported $ref format (only `#/$defs/<name>` and `#/definitions/<name>` are supported): {ref_str} What it means
parse_ref only accepts same-document references of the exact forms #/$defs/<name> and #/definitions/<name>. Any other $ref string — external file refs like 'shared.json#/Foo', JSON-Schema-2020 anchors like '#Foo', or OpenAPI-style '#/components/schemas/Foo' — is rejected before lookup because the inliner has no way to fetch or resolve it.
Source
Thrown at crates/language_model_core/src/tool_schema.rs:198
resolve_refs_recursive(item, defs, legacy_defs, visiting)?;
}
}
_ => {}
}
Ok(())
}
/// Parses a same-document `$ref` like `#/$defs/Foo` or `#/definitions/Foo`.
/// Returns `(defs_key, name)` where `defs_key` is the top-level key the
/// definition was looked up under, and `name` is the definition name.
fn parse_ref(ref_str: &str) -> Result<(&'static str, &str)> {
if let Some(name) = ref_str.strip_prefix("#/$defs/") {
return Ok(("$defs", name));
}
if let Some(name) = ref_str.strip_prefix("#/definitions/") {
return Ok(("definitions", name));
}
anyhow::bail!(
"Unsupported $ref format (only `#/$defs/<name>` and `#/definitions/<name>` are supported): {ref_str}"
);
}
fn adapt_to_json_schema_subset(json: &mut Value) -> Result<()> {
if let Value::Object(obj) = json {
const UNSUPPORTED_KEYS: [&str; 4] = ["if", "then", "else", "$ref"];
for key in UNSUPPORTED_KEYS {
anyhow::ensure!(
!obj.contains_key(key),
"Schema cannot be made compatible because it contains \"{key}\""
);
}
const KEYS_TO_REMOVE: [(&str, fn(&Value) -> bool); 6] = [
("format", |value| value.is_string()),
("additionalProperties", |_| true),View on GitHub (pinned to f4178619ac)
Solutions
- Pre-process the schema with an external dereferencer (jsonschema-ref-parser, datamodel-code-generator) that produces one self-contained document
- Rewrite OpenAPI '#/components/schemas/X' refs to '#/$defs/X' and hoist the components into $defs
- Replace anchor-based refs with named $defs entries
- After bundling, re-check for stray $ref keys — adapt_to_json_schema_subset also hard-rejects any remaining $ref
Example fix
// before
{ "$ref": "shared.json#/Point" }
// after
{
"$defs": { "Point": { "type": "object", "properties": { "x": { "type": "number" }, "y": { "type": "number" } } } },
"$ref": "#/$defs/Point"
} Defensive patterns
Strategy: validation
Validate before calling
fn only_supported_refs(schema: &serde_json::Value) -> bool {
let ok = true;
walk_refs(schema, &|r| r.starts_with("#/$defs/") || r.starts_with("#/definitions/"));
ok
}
// bundle first if this returns false:
// $RefParser.dereference(schema) (JS) | jsonschema_ref_resolver (Rust) Type guard
fn is_same_document_ref(ref_str: &str) -> bool {
ref_str.starts_with("#/$defs/") || ref_str.starts_with("#/definitions/")
} Prevention
- Dereference external/anchor refs into a single self-contained document before passing schemas to LLM tools
- Remember $ref is on the unsupported-keys list in adapt_to_json_schema_subset — the goal is full inlining
- Add a schema lint step to CI for tool schemas (no external refs, no if/then/else)
When it happens
Trigger: Passing a tool input_schema authored against JSON Schema 2019+ (anchor-based refs), a multi-file schema split across $id documents, or a schema converted from OpenAPI without rewriting component paths.
Common situations: Copy-pasting schemas from OpenAPI specs; using $defs with nested paths like '#/$defs/Foo/properties/bar' (also unsupported since only the top-level name is parsed); monorepos sharing schema files by relative path.
Related errors
- $ref target not found in {defs_key}: {ref_str}
- Bedrock does not support custom tools
- DeepSeek does not support custom tools
- llama.cpp does not support custom tools
- LM Studio does not support custom tools
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/6f7a5176b4489b3f.
Report an issue: GitHub.