Diagnostics configuration
Tuning diagnostics
The ruleset above is the default. A diagnostics block in glotto.yml tunes
it without touching the rules engine — it is a pure post-pass over the findings:
# glotto.yml
diagnostics:
rules:
no-error-response: error # promote a warning to a release-blocking error
mutation-no-idempotency-key: off # suppress a rule entirely
max_warnings: 10 # fail (exit 1) when warnings exceed this budget
diagnostics.rulesmaps a rule id to a severity override —offsuppresses the rule, whilewarn/errorremap its severity. Promoting awarningtoerrormakes it release-blocking; setting a rule tooffdrops its findings (and they no longer count towardmax_warnings). An unknown rule id is almost certainly a typo, so it’s reported as a non-fatal warning on stderr and otherwise ignored.diagnostics.max_warningsis a release-gating threshold: after overrides are applied, the remaining warning count exceeding this number fails the gate — even with zero errors. Start loose and ratchet it toward0as you approach1.0(see Release-gating guidance).
Both keys are optional and additive — a config with no diagnostics block lints exactly as the
defaults above.
The valid rule ids are the ones enumerated on this page, plus two that tune a config diagnostic rather than a spec rule:
config-entry-matched-nothing— the severity ofGLOTTO_CONFIG_ENTRY_MATCHED_NOTHING(below).offsilences it,errormakes it blocking. It’s tunable because a keyed entry naming an operation your spec no longer carries is legal by design — aglotto.ymlis meant to outlive a spec change — so a team deliberately carrying one can say so here instead of dropping--strict.config-auth-scheme-inert— the severity of the two inert halves ofGLOTTO_CONFIG_AUTH_SCHEME_REFUSED(below): a top-levelclient_settings.auth.header_prefixon an API whose security schemes cannot format one, and aclient_settings.auth.schemesblock on an API that declares a single security scheme. Both are the same kind of legal by design — aglotto.ymloutliving a change to your spec’s auth shape — so one off-switch covers them. It deliberately does not cover the third half, aheader_prefixon anapikey,basicorcustomscheme: no spec change makes that field applicable to those kinds, so there is nothing deliberate to declare.
The two are independent switches: they say different things (“my config outlives my spec’s operations” versus “my config outlives my spec’s auth shape”), so silencing one leaves the other reporting. The remaining config warnings report a directive Glotto structurally declined, which has no legal-by-design reading, and aren’t overridable.
Config diagnostics
The rules above lint your OpenAPI spec. Problems in your glotto.yml are reported by a
separate gate that validates the config against the schema, and it runs first — an invalid config
stops the pipeline before any spec rule is evaluated. glotto generate reports these on stderr.
Unlike the spec rules, they are keyed by a stable code and carry a source position
(file:line:col) into your glotto.yml:
| Code | Severity | Meaning |
|---|---|---|
GLOTTO_CONFIG_REQUIRED_FIELD_MISSING |
error | A required key is absent (e.g. organization.name). |
GLOTTO_CONFIG_INVALID_TYPE |
error | A value has the wrong type (e.g. a string where an object is expected). |
GLOTTO_CONFIG_INVALID_ENUM_VALUE |
error | A value is outside the allowed set (e.g. react_native.secure_storage, mcp.modes). |
GLOTTO_CONFIG_INVALID_VALUE |
error | A value fails a field-specific rule (e.g. max_delay < initial_delay; an operationId in both skip and only, or in both exclude and only). |
GLOTTO_CONFIG_EXCLUDE_ONLY_CONTRADICTION |
error | An exclude entry and an only key address the same operation using the two different spellings exclude accepts — e.g. exclude: ["post /pets"] alongside only: { createPet: … }. Contradictory intent, exactly as naming it the same way in both is: only restricts the operation to the listed targets, exclude withholds it from every artifact. exclude is applied first, so the operation is withheld everywhere and the only entry has no effect. Reported at generate time, where the spec is available to resolve the positional spelling. |
GLOTTO_CONFIG_INPUT_SOURCE |
error | Not exactly one of openapi / asyncapi / graphql is present. |
GLOTTO_CONFIG_OPENAPI_SOURCE_NOT_FOUND |
error | A local openapi.source path doesn’t resolve on disk. |
GLOTTO_CONFIG_OPENAPI_COMMAND_OUTPUT_PATH |
error | A command-source file output is absolute or escapes its working directory. Reported for a command source at any input site — openapi.source, asyncapi.source, graphql.source, graphql.operations, and each docs.versions[] snapshot — with the message naming the offending path; the code keeps its historical OPENAPI spelling so a config matching on it keeps working. |
GLOTTO_CONFIG_INTROSPECT_SOURCE_OPENAPI_ONLY |
error | An introspect source was written somewhere other than openapi.source. Introspection reads a framework’s source and synthesizes an OpenAPI document, so it cannot be what an asyncapi or graphql key means. Point the key at the document it describes, or move the introspect source to openapi.source. |
GLOTTO_CONFIG_DEFAULT_ENVIRONMENT |
error | default_environment names an environment that isn’t declared under environments. The message lists the names you did declare. This is an error rather than a fall-back-and-warn because there is no fallback that keeps what you meant: resolving it any other way would point your SDK, your docs playground and your MCP server at a host you wrote the key specifically to avoid. Fix the spelling, or add the environment. |
GLOTTO_CONFIG_DEFAULT_ENVIRONMENT_INFERRED |
warning | Your config declares two or more environments, none of them named production, and no default_environment — so Glotto used the first by sorted name, and the message says which. That rule exists so generated code never depends on the order you typed your YAML in; it is deliberately not a guess at which host you meant. Set default_environment to the one your SDKs, their README quickstarts, your docs playground and your MCP server should all point at. You will not see this for a single environment (nothing to choose between) or when one is named production (that one wins). |
GLOTTO_CONFIG_DOCS_VERSIONS |
error | A docs.versions entry has a non-route-safe slug, a duplicate slug, more than one default: true, or declares other than exactly one snapshot source (openapi / asyncapi / graphql). A slug is also rejected when the URL layer would rewrite it — the generated layout finds the current version by comparing the slug against a percent-encoded pathname segment, so a slug containing a space, a %, or a non-ASCII character never matches its own pages and every page under it silently falls back to the default version. Characters a URL path keeps verbatim (letters, digits, and -._~!$&'()*+,;=:@) are fine. |
GLOTTO_CONFIG_DOCS_REDIRECTS |
warning | A docs.redirects entry the generator will refuse to emit: an empty or non-string from/to, a from that is not site-absolute (it must start with /), a from or to containing whitespace (the emitted _redirects rule file is whitespace-delimited, so a host would read only the fragment before the space — percent-encode it), a from equal to its own to (a no-op loop), or a from an earlier entry already claims (the later one is unreachable). Also raised when your docs.deploy.target names a host whose own redirect config cannot express a from exactly — only a trailing * has an exact equivalent on Vercel, so an interior or repeated one is left out of the emitted vercel.json rather than rewritten into a rule that would match different URLs than you wrote. Only that entry is dropped — your other redirects still emit. |
GLOTTO_CONFIG_DOCS_STRUCTURED_DATA |
warning | A customer-authored docs.structured_data.extra_nodes / extra_nodes_by_route JSON-LD node the generator will refuse to carry: one that isn’t a JSON object, is an empty object, has no @type (a consumer dispatches on the type, so an untyped node is read by nothing), has a @type that isn’t a non-empty string — nor a non-empty array of them, JSON-LD’s multiple-type form — has an @id that isn’t a non-empty string or a @context that is neither a non-empty string nor an object, carries a nested @graph (that makes it a document rather than a node, and its members would be read as siblings of the derived ones), or exactly duplicates an earlier node in the same list. Only that node is dropped — your other nodes, and every node Glotto derives from your spec, still emit. This is a structural check: a misspelled @type, or a property that doesn’t belong to the type it sits on, is not validated here. |
GLOTTO_CONFIG_DOCS_OG_TEMPLATE |
error | A docs.og_images.template.colors value isn’t a colour the OG-card renderer can paint. Cards are rasterized by pureimage during your site’s astro build, not by a browser, so the accepted forms are #RGB / #RGBA / #RRGGBB / #RRGGBBAA, rgb(r,g,b) or rgba(r,g,b,a) with comma-separated numbers, and CSS named colours (rebeccapurple). hsl(), color(), gradients, var(--x), and the space-separated rgb(1 2 3) form are not supported — they either fail your build or paint a colour you didn’t ask for. |
GLOTTO_CONFIG_DOCS_DEPLOY_DOMAIN |
error | docs.deploy.custom_domain isn’t a bare DNS hostname. It becomes the authority of your docs site’s canonical URLs and the domain handed to your deploy provider’s binding, so write the hostname alone — docs.acme.com, not https://docs.acme.com, and no path, port, wildcard, or trailing dot. Labels are letters, digits, and hyphens, at most 63 characters each and 253 overall. Internationalized domains are supported in their punycode form (xn--caf-dma.example). |
GLOTTO_CONFIG_MCP_PACKAGE_NAME |
error | mcp.package_name isn’t a valid npm package name. It becomes the generated package.json "name" and — with any @scope/ stripped — the bin command name, so it must be at most 214 characters, lowercase, and made of letters, digits, and -._~, optionally with a single @scope/ prefix whose scope and name are each non-empty. Leave it unset to derive <your-org>-mcp instead. |
GLOTTO_CONFIG_CODE_OWNERS |
error | A code_owners rule the emission would have to guess about: a block that is declared but names no rules, a blank pattern, a rule with an empty owner list, an empty owner string, or an owner the forge your target’s repo lives on could not resolve. The message names the offending provider and the spellings it accepts — GitHub takes @login, @org/team-slug (exactly two segments) or an email address; GitLab additionally allows _ and . inside a segment and nests @group/subgroup/… without limit; Bitbucket and Azure Repos resolve an owner against your workspace directory, so anything non-empty is accepted here and checked when you apply it. This is an error rather than a dropped-and-warn because a CODEOWNERS line the forge cannot resolve is ignored in full — the path is left unowned, the file still exists, and nothing anywhere says so. |
GLOTTO_CONFIG_CODE_OWNERS_NOT_APPLIED |
warning | You declared code_owners, and this target does not receive a CODEOWNERS file. Two causes, both about the forge rather than your config. Bitbucket and Azure Repos read no such file — their equivalents are default reviewers and a required-reviewers branch policy, so Glotto applies the block through their APIs instead of emitting a file. A target with a repo_path lives in a subtree of a shared repo, and GitHub and GitLab read CODEOWNERS only from the repository root — a file emitted into the subtree would never be opened, so none is emitted; declare the ownership in the CODEOWNERS at that repository’s root instead. Reported by glotto generate. |
GLOTTO_CONFIG_DOCS_AUTH |
error | A docs.auth block omits a field its tier requires — password needs secret_env, sso needs both provider and login_url — or sets a non-positive session_hours. Supply the missing field (or drop the block to leave the docs site public). |
GLOTTO_CONFIG_DOCS_AUDIENCES |
error | A docs.audiences block names an audience in scopes or default that isn’t declared in members, or its members list is empty or has duplicates. Declare every audience in members first, and make default one of them. |
GLOTTO_CONFIG_DOCS_PERSONALIZATION |
error | A docs.personalization block has an empty fields allow-list, or a prefill_key_field that isn’t one of fields. Only fields on the allow-list are ever rendered into a page, so the key field has to be among them. |
GLOTTO_CONFIG_DOCS_I18N |
error | A docs.i18n block has a locale token that isn’t route-safe, a locales list that is empty or has duplicates, or a locales list that omits default_locale. Every locale becomes a URL segment, and the default has to be one of the locales you build. |
GLOTTO_CONFIG_CUSTOM_CASINGS |
error | A custom_casings key is not a lowercase alphanumeric word (identifier words are lowercased before matching, so API: API could never match — write api: API), or its rendering is not a pure re-casing of the key. Only letter case may differ; to change the word itself use naming (model members) or parameter_naming (method parameters). |
GLOTTO_CONFIG_RENAME_NOT_APPLIED |
warning | A naming member-rename targets an engine that doesn’t apply member renames — TypeScript and React Native (their DTOs are transparent, so the interface is the wire shape) or Python in the default non-pydantic mode — or an enums nominal-shaping directive targets an engine with no type-alias construct. The directive is accepted but the emitted member keeps its wire name. Drop it for that target, or use parameter_naming, which those engines do honor. |
GLOTTO_CONFIG_NAMESPACE_NOT_APPLIED |
warning | A targets.<slug>.namespace is set on one of the four SDKs whose published package name is its code identity — TypeScript and React Native (the npm package is the module you import), Dart (the pub package is the library you import), and Swift (the SwiftPM package is the module you import). There is no second name underneath it for namespace to set, so the key has no effect and the SDK keeps the identity package_name gives it. Set package_name for that target instead — for these four it is the code identity, and it also stays the name you publish under. namespace is honored for csharp, elixir, go, java, kotlin, php, python, ruby, and rust, where the two identities are genuinely separate (the Python import package vs. the PyPI distribution name; the Rust [lib] name vs. Cargo’s [package] name). |
GLOTTO_CONFIG_CASING_NOT_APPLIED |
warning | custom_casings is applied to model members, method parameters, and enum constants, but not yet to method names, class/type names, or resource accessors — those keep their default casing. Informational: no config change resolves it, and the warning clears when the remaining surfaces land. |
GLOTTO_SPEC_UNION_DEGRADED |
warning | A discriminated union won’t generate as a narrowed union (ADR-0041 eligibility); the SDK falls back to an open type. |
GLOTTO_CONFIG_MODEL_SHAPING_REFUSED |
warning | A models directive was declined — a rename onto a name another model holds, or an inline: true on a self-referencing model or a union member. The model stays as it was. |
GLOTTO_CONFIG_AUTH_SCHEME_REFUSED |
warning | A client_settings.auth directive was declined. Either a header_prefix was set on a scheme whose kind cannot carry one (apikey, basic, custom — only bearer and oauth2 format an Authorization value), or a top-level header_prefix was set on an API whose schemes are all of those kinds so it formats nothing, or a per-scheme schemes: block was written for an API that has a single security scheme, which is configured through client_settings.auth directly. A scheme name your spec no longer defines is not reported — a glotto.yml outlives a spec change. The last two are tunable with diagnostics.rules.config-auth-scheme-inert; the kind mismatch is not, because no spec change makes that field applicable. |
GLOTTO_CONFIG_MODEL_RENAMED |
warning | One of your models emits under a different type name for one target, because the name it would otherwise take is one that target’s own generated SDK source refers to — a schema named Data in a Swift SDK would land in the same module as the client’s own Data references and take them over, and Swift has no import-qualification escape for a same-module declaration. The generated name appends the target’s model suffix, and adds a number if that is taken too: Data → DataModel → DataModel2. Nothing is missing from the output and every reference to the model follows the new name; only the named target is renamed, so your other SDKs are unaffected. To pick the name yourself, set the key the warning opens with — a name.<target> entry under models. An explicit name is always honored and this warning stops. The key it names is the one you wrote, even when another models entry has already renamed that model, so it is always one your glotto.yml can carry. |
GLOTTO_CONFIG_SOFT_REQUIRED_SPLIT |
warning | A soft_required body_fields promotion generated a <Model>Request variant, because the operation’s request body is a model your API also returns — demanding the field on it would make it mandatory when decoding a response too. The shared model is unchanged; only this operation’s body uses the variant. |
GLOTTO_CONFIG_AUTO_POPULATE_REFUSED |
warning | An auto_populate position was declined — its schema doesn’t permit exactly one value, its one value has no sendable wire form, or the request body isn’t a JSON object. The input stays in the method signature. All four parameter locations (path, query, header, cookie) are supported. |
GLOTTO_CONFIG_DUAL_MODE_REFUSED |
warning | A streaming.dual_mode entry was declined, so the endpoint was not split into buffered and streaming methods — the operation isn’t in your spec, the spec never marks it as streaming, it takes no JSON-object request body, param_discriminator names a field that body doesn’t have, stream_event_model names a model your spec doesn’t define, the per-event type couldn’t be resolved (name it with stream_event_model), or the variant’s method name is already taken by a real operation (pick a different method_suffix). The operation keeps the single method it had. |
GLOTTO_CONFIG_ENTRY_MATCHED_NOTHING |
warning | An entry in one of the six operationId-keyed blocks — skip, only, exclude, parameter_naming, client_methods, aliases — names an operation your spec doesn’t carry, so it had no effect. Usually a typo; sometimes an operation renamed upstream since the config was written. The entry is still ignored rather than rejected (a glotto.yml outlives a spec change), so this is a warning you can tune or silence with diagnostics.rules.config-entry-matched-nothing. |
GLOTTO_CONFIG_POSITIONAL_PARAMS_REFUSED |
warning | A positional_params entry was declined — it names a parameter the operation doesn’t have, lists one twice, or places the $body token where it can’t be honored (the operation has no body, the body isn’t positional, taking it positionally would drag query parameters along, an optional body would precede a path parameter, or the token collides with a reserved name). The order is refused whole, so the method keeps its derived argument order — a partly-applied order would be a signature you never reviewed. |
GLOTTO_CONFIG_RESOURCE_METHOD_NAME |
error | A resources.<r>.methods.<key> declaration of the emitted method name could not be honored, so glotto generate stops. Either the endpoint beside it matches no operation in your spec (the verb and path must match exactly, placeholder names included — when exactly one operation serves the same route under a different placeholder name, get /pets/{id} against paths: /pets/{petId}, the message names the spelling your spec uses, which is usually a one-character fix), or the name it declares is already emitted by another operation on the same resource — in which case both operations are named and neither is renamed. Not suppressible: the alternative to stopping is shipping an SDK whose method names are not the ones you asked for, and a refused declaration would otherwise look exactly like one you never wrote. |
GLOTTO_SPEC_README_UNKNOWN_OPERATION |
warning | A readme.example_requests slot names an operation that is not an operationId in your spec. The README example falls back to the default selection. Usually a typo, or an operation renamed upstream. |
GLOTTO_SPEC_README_UNKNOWN_PARAM |
warning | A readme.example_requests.<slot>.params key matches no wire parameter of the operation it names — neither a path/query parameter nor a request-body field. The key is omitted from the rendered example. |
GLOTTO_CONFIG_UNKNOWN_TARGET |
error | A targets entry names no codegen engine, so glotto generate would refuse the config. The message lists the engines that exist. The usual cause is an emitted artifact that is not a target: mcp, docs and mock are configured by a top-level block of that name — see the MCP server guide — and the message says so for those three. Publishing is a different surface from the codegen targets: map: Glotto publishes the generated MCP server to npm for you, and that surface does accept mcp where targets: does not. |
GLOTTO_SPEC_README_SLOT_KIND_MISMATCH |
warning | A readme.example_requests pagination or streaming slot names an operation of the wrong kind (a non-paginated operation in the pagination slot, a non-streaming one in the streaming slot). That example falls back to the default selection. |
A config error blocks generation; the warnings don’t, but a strict gate turns them
(and any warning) into a non-zero exit — as does setting config-entry-matched-nothing: error, the
one severity you can raise on its own. Every warning below GLOTTO_SPEC_UNION_DEGRADED is computed
from your spec rather than from the config text, so they name the config path
(models.Address.name, soft_required.updateInvoice.body_fields.note,
parameter_naming.createPett) instead of a file:line:col position, and glotto generate prints
all of them on stderr as well — you don’t need a separate validate run to see a directive that
didn’t take. See the glotto.yml reference for the configuration surface
itself.