Skip to content

Cut cyclic $refs instead of leaving them dangling - #14

Merged
aszarama merged 1 commit into
masterfrom
as/resolve-cyclic-refs
Sep 24, 2026
Merged

aszarama merged 1 commit into
masterfrom
as/resolve-cyclic-refs

Conversation

@aszarama

Copy link
Copy Markdown
Contributor

🤖 Claude here, on Arek's behalf:

What

resolve_refs left a $ref in place when it hit a cycle. FastMCP moves each tool's input schema into $defs, where that $ref points to nothing, so every call to the tool failed argument validation:

PointerToNowhere: '/$defs/WorkflowActionSchema' does not exist within {...}

springdoc writes every polymorphic type as such a cycle: the parent lists its subtypes in oneOf, and each subtype extends the parent through allOf. So any tool whose input contains one is broken. The first two are createOrUpdateWorkflow and runWorkflow.

Fix

  • On the second expansion of a ref in one path, the resolver expands it without oneOf / anyOf / discriminator. A subtype keeps the fields it inherits from the parent, such as the type discriminator.
  • On a third repeat, it uses a {"type": ...} placeholder (with the description, if there is one).
  • No #/components/schemas/... $ref is left in a resolved path.

Verification

  • pytest tests --ignore=tests/integration --ignore=tests/manual: 27 passed. test_resolve_circular_refs asserted the old behavior (ref preserved), so I changed it. test_resolve_polymorphic_refs is new.
  • The tools on the current swagger.json get exactly the same input schemas: 53,509 bytes before and after.
  • Against a spec with the workflow write tools turned on, locally:
    • The dangling refs in createOrUpdateWorkflow went from 68 to 0.
    • Through a running server, createOrUpdateWorkflow (create + replace), runWorkflow, getWorkflowRun and the permission 403s all work end to end.

Worth knowing

With its subtypes inlined, the createOrUpdateWorkflow input schema is about 89 KB. It works, but it is large for an agent's tool list.

resolve_refs left a $ref in place when it hit a cycle. FastMCP moves the
schema into a tool's $defs, where that $ref points nowhere, so every call
to the tool failed argument validation with PointerToNowhere. springdoc
emits every polymorphic type as such a cycle (the parent lists subtypes in
oneOf, each subtype extends the parent through allOf), so this broke any
tool whose input includes one, such as createOrUpdateWorkflow and
runWorkflow.

Re-expand a cyclic ref once without its oneOf/anyOf/discriminator, so a
subtype keeps the fields it inherits, and replace any deeper repeat with a
{"type": ...} placeholder.
@aszarama
aszarama requested a review from hannavigil September 24, 2026 18:08
@aszarama
aszarama merged commit 7349e08 into master Sep 24, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants