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
Summary
Schema.toJsonSchemaDocumentdropstitle/description/examplesannotations onSchema.Number. The annotations are present on the AST; they just don't survive the lowering. Every other shape I tried keeps them somewhere.Reproduction
Output on
4.0.0-beta.107:Numberis 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:
A few observations that may help narrow it:
NullOrshows that ananyOfresult is not inherently the problem — it keepsdescriptionas a sibling ofanyOf.FiniteandInt(the checked numerics) keep the annotation, but nest it underallOfrather than putting it on the node.Number:.annotate()chained twice,.pipe(s => s.annotate(...)), as aSchema.Structproperty, and underSchema.optionalKeyall produce the same annotation-freeanyOf.ToJsonSchemaOptionshas no knob that affects it (generateDescriptionsandincludeAnnotationKeyare unrelated here), and the result is identical whichevertargetI pass.The 4-branch
anyOfemitted by theNumbercase ininternal/schema/toJsonSchemaDocument.tsalso 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.Finiteis a workable substitute for anyone hitting this (it keeps the annotation and lowers to a plainnumber), provided you also lift theallOfnesting.Environment
effect@4.0.0-beta.107