{"record":{"id":"d23143f5058981a7","repo":"facebook/relay","slug":"expected-client-edge-backing-field-to-be-transform","errorCode":null,"errorMessage":"Expected client edge backing field to be transformed into exactly one primitive.","messagePattern":"Expected client edge backing field to be transformed into exactly one primitive\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/crates/relay-codegen/src/build_ast.rs","lineNumber":2044,"sourceCode":"                 })\n            }\n        })\n    }\n\n    // This function creates a node that is the UNION of the nodes that would be created for read time resolvers\n    // and for exec time resolvers (so runtime has ALL the information it needs to run for both resolver modes.)\n    // For C2C (client-to-client) edges, we emit ClientEdgeToClientObject with model resolvers.\n    // For C2S (client-to-server) edges, we emit ClientEdgeToServerObject with the operation reference.\n    fn build_client_edge_exec_and_read_time(\n        &mut self,\n        context: &mut ContextualMetadata,\n        client_edge_metadata: ClientEdgeMetadata<'_>,\n    ) -> Primitive {\n        let backing_field_primitives =\n            self.build_selections_from_selection(context, client_edge_metadata.backing_field);\n\n        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. {:?}\",","sourceCodeStart":2026,"sourceCodeEnd":2062,"githubUrl":"https://github.com/facebook/relay/blob/668b1b85e06261aa3b58dabfc51f8b5524a70955/compiler/crates/relay-codegen/src/build_ast.rs#L2026-L2062","documentation":"While building a client edge (Relay resolver-backed edge) in codegen, the compiler transforms the backing field selection into primitives and expects exactly one result. If the backing field expanded into zero or multiple primitives (e.g. a fragment with multiple selections or an empty selection), this panic fires, meaning the client-edge metadata is inconsistent with the actual document.","triggerScenarios":"Compiling a @clientEdge/Relay resolver field whose backing_field selection builds to more or fewer than one primitive — e.g. the backing field is a fragment spread or inline fragment containing multiple selections instead of a single scalar/resolver field.","commonSituations":"Client schema extensions where the client edge backing field was edited to add extra fields; transforms that inlined fragments differently than expected; stale generated metadata after schema changes.","solutions":["Ensure the client edge backing field is a single scalar field (or single inlined resolver fragment resolving to one field) with no sibling selections.","Regenerate the client schema/extension so backing-field metadata matches the current document.","Update the relay compiler if a transform recently changed how backing fields are normalized.","Inspect the document with graphql-js validation/dump to see the actual backing field shape before compiling."],"exampleFix":"// before: backing fragment with multiple fields\nextend type Query { viewer: Viewer @clientEdge(backing: ViewerEdgeFragment) }\nfragment ViewerEdgeFragment on Query { viewer node(id: $id) }\n\n// after: single backing resolver field\nextend type Query { viewer: Viewer @clientEdge }\nfragment ViewerEdgeFragment on Query { viewer }","handlingStrategy":"validation","validationCode":"// Check client edge backing fragment has exactly one selection\nfunction validateBackingFragment(fragment) {\n  if (!fragment || fragment.selections.length !== 1) {\n    throw new Error('Client edge backing fragment must contain exactly one selection, got ' +\n      (fragment ? fragment.selections.length : 0));\n  }\n}","typeGuard":"function isSingleSelectionBacking(fragment) {\n  return fragment != null &&\n    Array.isArray(fragment.selections) &&\n    fragment.selections.length === 1;\n}","tryCatchPattern":"try {\n  buildAst(context, clientEdgeMetadata);\n} catch (e) {\n  if (String(e).includes('exactly one primitive')) {\n    console.error('Client edge backing field expanded to != 1 selection; inspect the backing fragment.');\n  } else throw e;\n}","preventionTips":["Keep client edge backing fragments to a single scalar selection","Regenerate client schema extensions after schema edits","Pin relay compiler version across CI and local runs","Add IR snapshots in tests for client edges"],"tags":["relay-compiler","client-edge","codegen","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"}