{"record":{"id":"5807e0750529cc2f","repo":"facebook/relay","slug":"unexpected-custom-directives","errorCode":null,"errorMessage":"Unexpected custom directives: {:#?}","messagePattern":"Unexpected custom directives: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/crates/relay-codegen/src/build_ast.rs","lineNumber":2367,"sourceCode":"                        selections: selections,\n                        type_: Primitive::SkippableNull,\n                        abstract_key: Primitive::SkippableNull,\n                    }));\n                    let aliased_fragment = Primitive::Key(self.object(object! {\n                        fragment: primitive,\n                        kind: Primitive::String(CODEGEN_CONSTANTS.aliased_inline_fragment_spread),\n                        name: Primitive::String(fragment_alias_metadata.alias.item),\n                    }));\n                    if let Some(catch_metadata) =\n                        CatchMetadataDirective::find(&inline_frag.directives)\n                    {\n                        self.build_catch_node(catch_metadata, aliased_fragment)\n                    } else {\n                        aliased_fragment\n                    }\n                } else {\n                    // TODO(T63559346): Handle anonymous inline fragments with no directives\n                    panic!(\n                        \"Unexpected custom directives: {:#?}\",\n                        inline_frag.directives\n                    );\n                }\n            }\n            Some(type_condition) => {\n                if self.variant == CodegenVariant::Normalization {\n                    let is_abstract_inline_fragment = type_condition.is_abstract_type();\n                    if is_abstract_inline_fragment {\n                        // Maintain a few invariants:\n                        // - InlineFragment (and `selections` arrays generally) cannot be empty\n                        // - Don't emit a TypeDiscriminator under an InlineFragment unless it has\n                        //   a different abstractKey\n                        // This means we have to handle two cases:\n                        // - The inline fragment only contains a TypeDiscriminator with the same\n                        //   abstractKey: replace the Fragment w the Discriminator\n                        // - The inline fragment contains other selections: return all the selections\n                        //   minus any Discriminators w the same key","sourceCodeStart":2349,"sourceCodeEnd":2385,"githubUrl":"https://github.com/facebook/relay/blob/668b1b85e06261aa3b58dabfc51f8b5524a70955/compiler/crates/relay-codegen/src/build_ast.rs#L2349-L2385","documentation":"When building an inline fragment in codegen, the compiler only supports inline fragments with no directives, @client, @defer/@stream-style metadata, @match, @module, or @catch. If an inline fragment carries other custom directives after client directives were handled, the compiler does not know how to represent them and panics, printing the full directive list.","triggerScenarios":"Compiling a document with an anonymous inline fragment carrying custom (non-Relay) directives, e.g. `... on Type @myDirective` or `... @include(if: $x)` in a position Relay's codegen does not support.","commonSituations":"Using server-only directives like @include/@skip on inline fragments in reader codegen paths; custom schema directives left on inline fragments; anonymous inline fragments (`... ` with no type condition) that Relay cannot type.","solutions":["Remove unsupported custom directives from the inline fragment or move the directive to a supported position (e.g. the parent field or a named fragment).","Give the inline fragment a type condition so Relay can emit it (TODO T63559346: anonymous inline fragments without directives are unsupported here).","Implement @include/@skip semantics via variables handled by transforms, or restructure the query so those directives apply to fields/spreads Relay supports.","If the directive is your own, strip it with a custom compiler transform before codegen."],"exampleFix":"// before\n... on User @myDirective { name }\n\n// after\n... on User { name }","handlingStrategy":"validation","validationCode":"const SUPPORTED = new Set(['client', 'defer', 'stream', 'match', 'module', 'catch', 'required', 'relay']);\nfunction validateInlineFragmentDirectives(fragment) {\n  for (const d of fragment.directives || []) {\n    if (!SUPPORTED.has(d.name.value)) {\n      throw new Error('Unsupported directive @' + d.name.value + ' on inline fragment');\n    }\n  }\n  if (!fragment.typeCondition && !(fragment.directives || []).length) {\n    console.warn('Anonymous inline fragment without directives is unsupported by relay codegen');\n  }\n}","typeGuard":"function hasOnlySupportedDirectives(fragment) {\n  const allowed = new Set(['client', 'defer', 'stream', 'match', 'module', 'catch', 'required', 'relay']);\n  return (fragment.directives || []).every(d => allowed.has(d.name.value));\n}","tryCatchPattern":"try {\n  relayCompiler.compile();\n} catch (e) {\n  if (String(e).includes('Unexpected custom directives')) {\n    console.error('Remove unsupported directives from inline fragments:', e.message);\n  } else throw e;\n}","preventionTips":["Do not put custom/server directives on inline fragments","Always give inline fragments a type condition","Strip custom directives with a compiler transform before codegen","Document allowed directives for your GraphQL authors"],"tags":["relay-compiler","graphql","inline-fragment","custom-directive","panic"],"backgroundTag":"unsupported-directive-on-fragment","analyzedSha":"668b1b85e06261aa3b58dabfc51f8b5524a70955","analyzedAt":"2026-09-02T19:57:20.783Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}