A custom GraphQL directive is applied in the SDL but has no effect in production — how do you find out why?
answer
- An ignored directive is a valid schema
- Three candidates, none of them noisy
- Compare the two name sets
- Test behaviour, not the annotation text
- Ask what rebuilds the schema afterwards
basics
~20 sAssume nothing reads it, because an ignored directive is a valid schema and raises no error. Check whether any handler matches that name, whether it visits that location kind, and whether a later rebuild or re-parse discarded the transform.
solid answer
~50 sSplit it into three candidates: nothing reads the directive at all, something reads it but not this location kind, or something read it and a later schema rebuild threw the result away. None of the three errors, because a defined directive applied at an allowed location with valid arguments is a valid schema whatever the code does. So test the behaviour rather than the annotation — execute a document and compare the returned value against the source value — and inspect the schema the server actually built rather than the SDL file. The fastest single check is diffing the set of directive names applied anywhere in the SDL against the set any handler claims to consume; a rename on one side of that string coupling is the classic cause. Then make it loud: fail the build when an applied custom directive matches no handler, or when a handler matches zero locations, and keep one executed contract test per directive.
code
graphql · 11 linesdirective @unit(symbol: String!) on FIELD_DEFINITION
type Animal {
earTag: String!
birthWeight: Float! @unit(symbol: "kg")
sire: Animal
dam: Animal
}
# The build-time pass still matches the pre-rename name "weight".
# It wraps nothing, reports nothing, and the schema is valid.go deeper
Take away the core fact: a directive nothing reads produces no error anywhere, so a green build and a valid schema are not evidence that the annotation is doing anything.
Be able to walk the checks in order — does any handler match this name, does it visit this location kind, does anything rebuild the schema afterwards — and to test the executed value rather than the SDL text.
Show the production instinct: name the string coupling between SDL and code, diff the two name sets, and then convert the silent failure into a build failure instead of patching the single instance.
Own the guardrail policy. Decide what the build must refuse — unread directives, handlers matching nothing — and treat a directive name as public API of the schema build, renamed only as a coordinated, verified change.
### Start by separating three questions "The directive is in the SDL but does nothing" is three distinct failures wearing one costume, and the diagnosis is a matter of deciding which: 1. **Nothing ever read it.** No handler matches that directive name at all. 2. **Something reads it, but not this application.** The handler covers one location kind and this application sits on another, or on a part of the schema the pass never visited. 3. **Something read it, and the result was thrown away.** The schema was rebuilt, re-printed or re-parsed after the pass, so the annotation survived and the behaviour did not. None of the three produces an error. A defined directive applied at an allowed location with well-typed arguments is a valid schema whether or not a single line of code cares about it. That is the first thing to say, and it reframes the hunt: you are not looking for a broken feature, you are looking for a feature that was never wired. ### A worked incident A four-person platform team owns a livestock pedigree graph. `Animal.birthWeight` is stored in pounds and annotated so the API returns kilograms. During a schema tidy-up the directive was renamed from `@weight` to `@unit` and every application in the SDL was updated in the same commit — a clean, reviewable change. The build-time pass that wraps resolvers still matched the string `"weight"`. It matched nothing, wrapped nothing, and reported nothing. The schema validated. Tests that asserted on the SDL passed. The mobile release that shipped two days later displayed pound values labelled kg, and nobody noticed until a breeder queried a calf weighing 96.4. The lesson generalises: an applied directive is a *string-keyed coupling* between SDL text and server code, and neither the schema validator nor the type checker sees across that seam. ### How to actually confirm it * **Test the behaviour, never the annotation.** Execute a document against the running schema and compare the returned field value against the raw source value. A test asserting the SDL contains `@unit` is exactly the test that passed through this incident. * **Inspect the schema the server built, not the file on disk.** Ask the runtime schema whether the application is present on that field definition, and whether the resolver installed there is the original or a wrapper. Note that introspection will not help you here — applications of custom directives are not exposed through it, which is precisely why this class of bug is quiet. * **Diff the two name sets.** Collect every directive name applied anywhere in the SDL, collect every name any handler claims to consume, and print the symmetric difference. In this incident that one diff names the bug instantly. * **Ask what runs after the pass.** Any step that merges more SDL, re-prints the schema for a registry and re-parses it, or reconstructs the schema for a second purpose can hand you an annotated-but-unwrapped schema. * **Check location coverage.** If the directive is declared for several locations but the walk only visits field definitions, applications on arguments or enum values do nothing while the ones on fields work — which reads as "intermittently broken" and wastes an afternoon. ### Making the failure loud instead The durable fix is not this one rename; it is removing the silence. * **Fail the build on an unread directive.** After the pass, assert that every custom directive applied in the schema was claimed by some handler, and that every handler matched at least one location. Zero matches is almost always a bug, never an intention. * **Derive the name from one source.** Register the handler and generate or validate the directive definition from the same constant so a rename cannot desynchronise them. * **Own a contract test per directive.** One executed query per directive, through the real build pipeline, asserting the transformed value. Cheap, and it is the only test that would have caught this. * **Treat a directive name as public API of the schema build.** Rename it the way you would rename a field: as a coordinated change with a check that proves the new name is live. ### What the interviewer is listening for They want to hear you say "the specification gives this no behaviour, so something in *our* build must be reading it, and my first question is what" — and then hear you make the failure loud rather than fixing the single instance. Candidates who reach straight for introspection, or who assume a validation error must exist somewhere, have not internalised that an ignored directive is a perfectly valid schema.
- Why will introspecting the deployed schema not tell you whether the directive is being honoured?Introspection reports types, fields and arguments, and it would not tell you which resolver is installed even if it did surface the application. The behaviour lives in code, so the only observation that settles it is executing the field and comparing the result against the underlying value. Treat introspection as a description of the contract, never as evidence about the implementation.
- The directive works on some annotated fields and not others. What does that pattern suggest?Usually partial coverage rather than a total miss: the pass visits one location kind and not another, or it walks types reachable from the root and misses ones merged in later, or a second schema assembly path skips it. Compare a working and a non-working application side by side — location kind, which SDL file it came from, and whether that part of the schema existed when the pass ran.
- How do you stop a rename from silently disconnecting a directive again?Remove the string coupling and remove the silence. Derive the directive definition and the handler registration from one shared constant so they cannot drift, fail the build when any applied custom directive has no consumer or any handler matches zero locations, and keep one executed test per directive that asserts the transformed value rather than the presence of the annotation.
saying these in an interview costs you the question
- Expects a validation error for an unread directive
- Tests that the SDL text contains the directive
- Trusts introspection to prove the behaviour runs
- Fixes the one field instead of the silent failure mode
- Never considers a rebuild after the transform ran
- Assumes the directive works because the schema deploys