From 301bf519ab9241f9be4c333e3676e7c4e6013583 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:20:10 +0200 Subject: [PATCH] fix: resolve circular OpenAPI schema refs in tool server specs (#27413) OpenAPI specs with circular schema references, such as Mealie's where Recipe and RecipeCategory reference each other through properties and array items, crashed convert_openapi_to_tool_payload with a RecursionError, so the tool server produced no specs and the integration never appeared in the model or tool selection. resolve_schema already had a visited-set guard against circular references, but the recursive calls for properties and items dropped the set, so cycles running through those edges were never detected. This threads the visited set through those calls and passes a per-path copy when following a $ref, so only true ancestor cycles are pruned to an empty schema while sibling references to the same schema still resolve fully. Fixes #27239. --- backend/open_webui/utils/tools.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index c78b315304..2ef2b97c11 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -913,23 +913,22 @@ def resolve_schema(schema, components, resolved_schemas=None): # Avoid infinite recursion on circular references return {} - resolved_schemas.add(schema_name) - ref_parts = ref_path.strip('#/').split('/') resolved = components for part in ref_parts[1:]: # Skip the initial 'components' resolved = resolved.get(part, {}) - return resolve_schema(resolved, components, resolved_schemas) + # Per-path visited set so sibling refs to the same schema still resolve + return resolve_schema(resolved, components, resolved_schemas | {schema_name}) resolved_schema = copy.deepcopy(schema) # Recursively resolve inner schemas if 'properties' in resolved_schema: for prop, prop_schema in resolved_schema['properties'].items(): - resolved_schema['properties'][prop] = resolve_schema(prop_schema, components) + resolved_schema['properties'][prop] = resolve_schema(prop_schema, components, resolved_schemas) if 'items' in resolved_schema: - resolved_schema['items'] = resolve_schema(resolved_schema['items'], components) + resolved_schema['items'] = resolve_schema(resolved_schema['items'], components, resolved_schemas) # Resolve composition keywords (oneOf, anyOf, allOf) which may contain $ref for keyword in ('oneOf', 'anyOf', 'allOf'):