Skip to content

Schema.toJsonSchemaDocument drops annotations on Schema.Number #7192

Description

@spencerbeggs

Draft. Filing now to get the observation on record — a minimal, self-contained reproduction repo is coming and I'll follow up on this issue with it. Everything below was observed directly against effect@4.0.0-beta.107, but I haven't yet reduced it past the snippet here.

Summary

Schema.toJsonSchemaDocument drops title / description / examples annotations on Schema.Number. The annotations are present on the AST; they just don't survive the lowering. Every other shape I tried keeps them somewhere.

Reproduction

import { Schema } from "effect"

const j = (s: any) => JSON.stringify(Schema.toJsonSchemaDocument(s).schema)

console.log("Number :", j(Schema.Number.annotate({ description: "d" })))
console.log("String :", j(Schema.String.annotate({ description: "d" })))
console.log("Finite :", j(Schema.Finite.annotate({ description: "d" })))
console.log("Int    :", j(Schema.Int.annotate({ description: "d" })))
console.log("NullOr :", j(Schema.NullOr(Schema.String).annotate({ description: "d" })))

Output on 4.0.0-beta.107:

Number : {"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}
String : {"type":"string","description":"d"}
Finite : {"type":"number","allOf":[{"description":"d"}]}
Int    : {"type":"integer","allOf":[{"description":"d"}]}
NullOr : {"anyOf":[{"type":"string"},{"type":"null"}],"description":"d"}

Number is the only one where the annotation disappears entirely.

Notes

The annotation is definitely on the AST — it's lost during lowering, not at annotation time:

Schema.Number.annotate({ description: "d" }).ast.annotations
// => { description: "d" }

A few observations that may help narrow it:

  • NullOr shows that an anyOf result is not inherently the problem — it keeps description as a sibling of anyOf.
  • Finite and Int (the checked numerics) keep the annotation, but nest it under allOf rather than putting it on the node.
  • No authoring form I tried recovers it for Number: .annotate() chained twice, .pipe(s => s.annotate(...)), as a Schema.Struct property, and under Schema.optionalKey all produce the same annotation-free anyOf.
  • ToJsonSchemaOptions has no knob that affects it (generateDescriptions and includeAnnotationKey are unrelated here), and the result is identical whichever target I pass.

The 4-branch anyOf emitted by the Number case in internal/schema/toJsonSchemaDocument.ts also arrives at the caller compacted to 2 branches, so the value being annotated may not be the representation the annotations are attached to. I have not confirmed that — it's a guess, and it's the part the minimal reproduction is meant to settle.

Impact

Consumers generating JSON Schema for numeric fields lose their documentation silently — no error, no warning, just a missing description. In our case it stripped descriptions from 29 numeric fields across an MCP tool surface, which we only caught because one test asserted on it.

Schema.Finite is a workable substitute for anyone hitting this (it keeps the annotation and lowers to a plain number), provided you also lift the allOf nesting.

Environment

  • effect@4.0.0-beta.107
  • Node 26.6.0, macOS arm64

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions