Skip to content

fix(providers): strip $schema recursively and downgrade fallback log level#762

Merged
penso merged 1 commit intomainfrom
tough-net
Apr 17, 2026
Merged

fix(providers): strip $schema recursively and downgrade fallback log level#762
penso merged 1 commit intomainfrom
tough-net

Conversation

@penso
Copy link
Copy Markdown
Collaborator

@penso penso commented Apr 17, 2026

Summary

Fixes #760 — schema normalization WARN logs repeated 1000+ times per session.

  • Recursive $schema stripping: json_schema_ast validates $schema at every nesting level, so root-only stripping was insufficient for schemas with $schema inside nested definitions/$defs (e.g. Attio MCP tools). Now strips recursively before canonicalization.
  • Downgraded log level: The fallback path works correctly — logging at warn! was inappropriate for an expected state. Changed to debug! per project logging conventions.

Validation

Completed

  • cargo +nightly-2025-11-30 fmt --all -- --check
  • just lint (clippy clean)
  • just test (367 tests pass)
  • New test: sanitize_draft07_nested_definitions_schema_canonicalized — verifies schemas with $schema in nested definitions go through full canonicalization (not fallback)
  • New test: sanitize_draft07_schema_uses_canonicalization_not_fallback — verifies root-level draft-07 schemas also trigger canonicalization
  • Both tests detect the issue before the fix (verified by reverting and re-running)

Manual QA

  1. Run moltis serve with an OpenAI-compatible provider (e.g. OpenRouter)
  2. Connect MCP tools that use draft-07 schemas (e.g. Attio)
  3. Start a chat session and trigger tool calls
  4. Check ~/.config/moltis/logs.jsonl — no more WARN spam from schema_normalization
  5. Verify tools still function correctly (schemas are normalized via canonicalization, not fallback)

🤖 Generated with Claude Code

…level (#760)

The schema normalization module logged a WARN on every tool schema that
used a non-Draft-2020-12 `$schema` URI (e.g. draft-07 from Attio MCP
tools). Two problems:

1. `$schema` was only stripped from the root of the parameters object.
   `json_schema_ast` validates `$schema` at every nesting level, so
   schemas with `$schema` inside nested `definitions`/`$defs` would
   still fail canonicalization and fall through to the WARN path.

2. The fallback log level was `warn!`, but this is an expected state
   for non-standard schemas where the fallback works correctly. Per
   the project's logging conventions, `debug!` is appropriate for
   "detailed diagnostics" rather than "unexpected but recoverable."

Fix: replace root-only stripping with `strip_schema_keyword_recursive`
that removes `$schema` from all levels, and downgrade the three fallback
log statements from `warn!` to `debug!`.

Entire-Checkpoint: d9484974c5c2
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 17, 2026

Greptile Summary

Fixes repeated WARN-level log spam (#760) caused by $schema keywords embedded inside nested definitions/$defs blocks of MCP tool schemas (e.g. Attio). The previous root-only strip left nested $schema URIs intact, causing json_schema_ast to reject the schema at inner pointers and fall through to the best-effort path on every tool call. The recursive stripping function resolves the root cause, and the log-level downgrade to debug! is consistent with project conventions for expected-recoverable states.

Confidence Score: 5/5

Safe to merge — targeted fix with no correctness risk and two regression tests that verify the non-fallback code path.

No P0/P1 findings. The recursive stripping is correct (handles objects and arrays, removes $schema before recursing into sibling values). The debug! downgrade matches CLAUDE.md conventions for expected-recoverable states. Both new tests are well-designed: they detect the fallback path indirectly via the lower_boolean_and_null_types canonicalization side-effect, and the PR description confirms they failed before the fix.

No files require special attention.

Important Files Changed

Filename Overview
crates/providers/src/openai_compat/schema_normalization.rs Adds recursive $schema stripping via strip_schema_keyword_recursive and downgrades three fallback log levels from warn! to debug! — both changes are correct and align with project conventions.
crates/providers/src/openai_compat/tests.rs Adds two well-designed regression tests that verify canonicalization runs (not the fallback path) by asserting the lower_boolean_and_null_types side-effect (enum: [false, true]) is present after sanitization.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["sanitize_schema_for_openai_compat(schema)"] --> B["canonicalize_schema_for_openai_compat(schema)"]
    B --> C["schema.clone() → input"]
    C --> D["strip_schema_keyword_recursive(&mut input)\n(NEW: recurses into objects + arrays)"]
    D --> E["SchemaDocument::from_json(&input)"]
    E -->|Err| F["debug! preflight failed\nreturn input as-is"]
    E -->|Ok| G["document.root()"]
    G -->|Err| H["debug! AST resolution failed\nreturn input as-is"]
    G -->|Ok| I["document.canonical_schema_json()"]
    I -->|Err| J["debug! canonicalization unavailable\nreturn input as-is"]
    I -->|Ok| K["canonical serde_json::Value"]
    K --> L["Schema::try_from(canonical)"]
    L --> M["Apply schemars transforms\nReplaceConst / ReplaceUnevaluatedProperties\nReplacePrefixItems / RemoveRefSiblings\nOpenAiSchemaSubset / SimplifyComposite\nPruneOrphanedRequired / RestoreEnumType"]
    M --> N["*schema = transformed.to_value()"]
Loading

Reviews (1): Last reviewed commit: "fix(providers): strip $schema recursivel..." | Re-trigger Greptile

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 17, 2026

Codecov Report

❌ Patch coverage is 88.23529% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...roviders/src/openai_compat/schema_normalization.rs 88.23% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented Apr 17, 2026

Merging this PR will not alter performance

✅ 39 untouched benchmarks
⏩ 5 skipped benchmarks1


Comparing tough-net (77abc54) with main (c733c91)

Open in CodSpeed

Footnotes

  1. 5 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@penso penso merged commit 5c19899 into main Apr 17, 2026
42 of 46 checks passed
@penso penso deleted the tough-net branch April 17, 2026 09:17
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.

[Bug]: schema_normalization WARN logs repeated 1000+ times per session

1 participant