{"record":{"id":"8215cb591084468d","repo":"facebook/relay","slug":"shadow-resolver-with-returnfragment-must-declare","errorCode":null,"errorMessage":"shadow resolver with @returnFragment must declare a @rootFragment","messagePattern":"shadow resolver with @returnFragment must declare a @rootFragment","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/crates/relay-transforms/src/relay_resolvers/spread_transform.rs","lineNumber":277,"sourceCode":"        // where the resolver's returned pointer targets the same record the\n        // shadowed field navigates to -- entirely from the store, with no network\n        // roundtrip. When the pointer instead targets a different server object,\n        // that record's selections are absent from the store and the runtime's\n        // client-edge availability check fires the generated `ClientEdgeQuery`\n        // refetch. The two arms are complementary: the transplant populates the\n        // common case, the refetch backstops the cross-object case, and the\n        // runtime selects between them per read based on what is in the store.\n\n        // A magic fragment (one declaring a `@returnFragment`) is required to\n        // also declare a `@rootFragment` (enforced by the\n        // `ReturnFragmentRequiresRootFragment` validation), and that root fragment\n        // must be present in the program by the time the spread transform runs. A\n        // missing root fragment here is therefore a compiler bug, not a user\n        // error -- fail loudly rather than silently skipping the transplant\n        // (which would drop the consumer's selections from the main operation).\n        let root_fragment_name = field_metadata\n            .fragment_name\n            .expect(\"shadow resolver with @returnFragment must declare a @rootFragment\");\n        let root_fragment = self.program.fragment(root_fragment_name).unwrap_or_else(|| {\n            panic!(\"shadow resolver root fragment `{root_fragment_name}` must be present in the program\")\n        });\n\n        // Clone the root-fragment path from the root down to the shadowed field\n        // (the one carrying the `ShadowReturnMarker` for this resolver's return\n        // fragment), preserving every ancestor linked field / inline fragment /\n        // condition along with its directives and arguments. At the marked field\n        // we splice in the consumer's selections (re-bound onto the shadowed\n        // server type). Returns `None` (no marker found) only on an unvalidated\n        // magic-fragment edge, which earlier validation passes prevent.\n        // Build a substitution from the root fragment's local argument variables\n        // (its `@argumentDefinitions`) to the values the consumer passed at the\n        // resolver field call site. The transplanted path is spliced directly into\n        // the consumer operation, so a reference to a root-fragment argument\n        // variable (e.g. `nodes(ids: $ids)`) must be remapped to the consumer's\n        // value (e.g. `$mixed_campaign_group_ids`); otherwise the operation would\n        // reference an undefined variable. For the normal (non-transplanted)","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/facebook/relay/blob/668b1b85e06261aa3b58dabfc51f8b5524a70955/compiler/crates/relay-transforms/src/relay_resolvers/spread_transform.rs#L259-L295","documentation":"In shadow_transplant_selection, a shadow resolver carrying @returnFragment must have a @rootFragment-derived fragment_name in its metadata. The code explicitly documents that a missing root fragment here is a compiler bug, not a user error, and panics so the failure is loud instead of silently dropping the consumer's selections from the main operation.","triggerScenarios":"A shadowed field's resolver metadata lacks fragment_name when the spread transform transplants the shadow fragment's selections into the main operation — i.e. the @returnFragment lowering did not produce a root fragment entry.","commonSituations":"Hand-edited or partially applied transform pipeline; a Relay version mismatch between the codegen/runtime and the compiler crates; a compiler regression around @returnFragment.","solutions":["Ensure the pass that resolves @returnFragment into @rootFragment metadata runs before the spread transform.","Compile the whole program together so the root fragment definition exists in the program (the companion panic covers the missing-definition case).","Capture the failing document and file a relay compiler bug — the comment says this is never a user error.","Bisect relay compiler versions to find the regression."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"assert!(field_metadata.fragment_name.is_some(), \"@returnFragment resolver `{}` must declare @rootFragment\", field_metadata.name);","typeGuard":"fn has_root_fragment(m: &FieldMetadata) -> bool { m.fragment_name.is_some() }","tryCatchPattern":"let Some(root_fragment_name) = field_metadata.fragment_name else {\n    return Err(Diagnostic::error(\"@returnFragment resolver missing @rootFragment\"));\n};","preventionTips":["Keep @returnFragment lowering passes before the spread transform.","Bisect relay versions when this appears after an upgrade.","Treat this panic as a compiler bug and file a repro."],"tags":["relay-compiler","returnfragment","internal-invariant"],"backgroundTag":"relay-compiler-internal-invariant","analyzedSha":"668b1b85e06261aa3b58dabfc51f8b5524a70955","analyzedAt":"2026-09-02T19:57:20.783Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}