{"record":{"id":"0a6d02516bfe0a2b","repo":"facebook/relay","slug":"expected-client-edge-backing-field-to-be-a-relay-r","errorCode":null,"errorMessage":"Expected Client Edge backing field to be a Relay Resolver. {:?}","messagePattern":"Expected Client Edge backing field to be a Relay Resolver\\. (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/crates/relay-codegen/src/build_ast.rs","lineNumber":2061,"sourceCode":"        if backing_field_primitives.len() != 1 {\n            panic!(\n                \"Expected client edge backing field to be transformed into exactly one primitive.\"\n            )\n        }\n        let backing_field = backing_field_primitives.into_iter().next().unwrap();\n\n        let selections_item = self.build_linked_field(context, client_edge_metadata.linked_field);\n\n        match &client_edge_metadata.metadata_directive {\n            ClientEdgeMetadataDirective::ClientObject {\n                model_resolvers, ..\n            } => {\n                let field_directives = match &client_edge_metadata.backing_field {\n                    Selection::ScalarField(field) => Some(&field.directives),\n                    // Although the reader checks for FragmentSpreads on the backing field, the normalization\n                    // transforms inline the fragment spread so we match an InlineFragment here\n                    Selection::InlineFragment(inline_frag) => Some(&inline_frag.directives),\n                    _ => panic!(\n                        \"Expected Client Edge backing field to be a Relay Resolver. {:?}\",\n                        client_edge_metadata.backing_field\n                    ),\n                };\n                let client_edge_model_resolvers = field_directives.and_then(|field_directives| {\n                    let resolver_metadata = RelayResolverMetadata::find(field_directives).unwrap();\n                    let is_weak_resolver = matches!(\n                        resolver_metadata.output_type_info,\n                        ResolverOutputTypeInfo::Composite(_)\n                    );\n                    let model_resolver_primitives = if !is_weak_resolver {\n                        self.build_client_edge_model_resolvers(model_resolvers, resolver_metadata)\n                    } else {\n                        vec![]\n                    };\n                    if model_resolver_primitives.is_empty() {\n                        None\n                    } else {","sourceCodeStart":2043,"sourceCodeEnd":2079,"githubUrl":"https://github.com/facebook/relay/blob/668b1b85e06261aa3b58dabfc51f8b5524a70955/compiler/crates/relay-codegen/src/build_ast.rs#L2043-L2079","documentation":"When reading directives of a client edge backing field, the compiler only accepts ScalarField or InlineFragment selections (both can carry Relay Resolver metadata). Any other selection kind (LinkedField, FragmentSpread at this stage, etc.) reaching this match means the backing field is not a Relay Resolver-backed field, so it panics with the selection dumped.","triggerScenarios":"Compiling a client edge whose backing field is a Selection::LinkedField or any selection other than a scalar field / inline fragment after normalization transforms.","commonSituations":"Defining a client edge over a linked (object) field instead of a resolver scalar; transform pipeline differences where the backing fragment spread was not inlined to an InlineFragment; hand-edited client schema extensions.","solutions":["Make the client edge backing field a Relay Resolver scalar field (or an inline fragment that normalizes to one).","Confirm the normalization transforms that inline fragment spreads ran before codegen; align compiler version with your transform config.","Check RelayResolverMetadata is attached via @relay_resolver / RescriptRelay resolver directives on the backing field.","Dump the normalized IR to verify which selection kind the backing field became."],"exampleFix":"// before: backing field is a linked field\nextend type Query { items: [Item] @clientEdge }\n\n// after: backing field is a resolver scalar\nextend type Query { itemResolver: Item @relay_resolver(root_fragment_name: \"ItemResolverFragment\") @clientEdge }","handlingStrategy":"type-guard","validationCode":"function validateClientEdgeBacking(selection) {\n  const ok = selection.kind === 'ScalarField' || selection.kind === 'InlineFragment';\n  if (!ok) {\n    throw new Error('Client edge backing field must be a scalar or inline fragment, got ' + selection.kind);\n  }\n}","typeGuard":"function isResolverBackableSelection(s) {\n  return s != null && (s.kind === 'ScalarField' || s.kind === 'InlineFragment');\n}","tryCatchPattern":"try {\n  compile();\n} catch (e) {\n  if (String(e).includes('Client Edge backing field to be a Relay Resolver')) {\n    console.error('Backing field selection kind not resolver-compatible:', e.message);\n  } else throw e;\n}","preventionTips":["Define client edges only over Relay resolver scalar fields","Confirm normalization transforms inline backing spreads","Inspect normalized IR when changing client edge definitions","Keep compiler/tooling versions aligned"],"tags":["relay-compiler","client-edge","resolver","panic"],"backgroundTag":"invalid-client-edge-definition","analyzedSha":"668b1b85e06261aa3b58dabfc51f8b5524a70955","analyzedAt":"2026-09-02T19:57:20.783Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}