{"record":{"id":"3bcc2547815a7ff1","repo":"facebook/relay","slug":"cannot-have-module-selections-at-multiple-paths-u","errorCode":null,"errorMessage":"Cannot have @module selections at multiple paths unless the selections are within fields.","messagePattern":"Cannot have @module selections at multiple paths unless the selections are within fields\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/crates/relay-transforms/src/match_/match_transform.rs","lineNumber":428,"sourceCode":"                    .collect::<Vec<&str>>()\n                    .join(\"_\");\n                match_directive_key_argument =\n                    format!(\"{match_directive_key_argument}_{alias_path_str}\").intern();\n            }\n\n            // If this is the first time we are encountering @module at this path, also ensure\n            // that we have not previously encountered another @module associated with the same\n            // match_directive_key_argument.\n            //\n            // This ensures that all of the @module's associated with a given @match occur at\n            // a single path.\n            let matches = match self.matches_for_path.get_mut(&self.path) {\n                None => {\n                    let existing_match_with_key = self.matches_for_path.values().any(|entry| {\n                        entry.match_directive_key_argument == match_directive_key_argument\n                    });\n                    if existing_match_with_key {\n                        let parent_name = parent_name.expect(\"Cannot have @module selections at multiple paths unless the selections are within fields.\");\n                        return Err(Diagnostic::error(\n                            ValidationMessage::InvalidModuleSelectionWithoutKey {\n                                document_name: self.document_name,\n                                parent_name: parent_name.item,\n                            },\n                            parent_name.location,\n                        ));\n                    }\n                    self.matches_for_path.insert(\n                        self.path.clone(),\n                        Matches {\n                            match_directive_key_argument,\n                            types: Default::default(),\n                        },\n                    );\n                    self.matches_for_path.get_mut(&self.path).unwrap()\n                }\n                Some(matches) => matches,","sourceCodeStart":410,"sourceCodeEnd":446,"githubUrl":"https://github.com/facebook/relay/blob/668b1b85e06261aa3b58dabfc51f8b5524a70955/compiler/crates/relay-transforms/src/match_/match_transform.rs#L410-L446","documentation":"The @match/@module validation rejects documents where @module selections appear at multiple paths without being nested inside fields. When a duplicate module key is found at another path but the parent is not a field (parent_name is None — e.g. the selection sits directly at a fragment root), the code panics on parent_name.expect(...) instead of emitting the InvalidModuleSelectionWithoutKey diagnostic.","triggerScenarios":"Placing @module inline fragments/spreads directly at a fragment's top level (not under a concrete field) such that the same module key argument appears at more than one path: matches_for_path already holds an entry with that match_directive_key_argument and parent_name is None.","commonSituations":"Authoring a fragment with two @module fragments at sibling top-level positions; a refactor moved a @module fragment out from under its parent field; programmatically generated fragments lacking a field wrapper; assuming @module fragments can float at fragment root.","solutions":["Nest each @module inline fragment under a concrete field so every selection has a distinct path and a parent field name.","De-duplicate module keys so a given key argument appears at only one path, or give each path a distinct key argument.","Rewrite the fragment so module selections are never directly at the fragment root; wrap them in selections on fields.","If parallel module selections are intended, split them into separate fragments.","If a seemingly valid document hits this, minimize it and file an issue — this branch should produce a diagnostic, not a panic."],"exampleFix":"// before\nfragment F on Query {\n  ... on Post @module(name: \"PostBody\") { body }\n  ... on Story @module(name: \"PostBody\") { body }\n}\n// after\nfragment F on Query {\n  post { ... on Post @module(name: \"PostBody\") { body } }\n  story { ... on Story @module(name: \"PostBodyStory\") { body } }\n}","handlingStrategy":"validation","validationCode":"function validateModuleSelectionPaths(fragment) {\n  const seen = new Map(); // key argument -> path\n  for (const sel of flattenSelections(fragment.selectionSet)) {\n    if (sel.kind !== 'InlineFragment' || !hasModuleDirective(sel)) continue;\n    const key = getModuleKeyArg(sel);\n    const underField = isUnderFieldSelection(sel);\n    if (seen.has(key) || !underField) {\n      return { ok: false, key, underField };\n    }\n    seen.set(key, pathOf(sel));\n  }\n  return { ok: true };\n}\n// before compiling: const r = validateModuleSelectionPaths(fragment); if (!r.ok) throw ...","typeGuard":"function isModuleSelectionWithinField(node) {\n  let p = node.parent;\n  while (p) {\n    if (p.kind === 'Field') return true;\n    if (p.kind === 'Fragment' || p.kind === 'Operation') return false;\n    p = p.parent;\n  }\n  return false;\n}","tryCatchPattern":"try {\n  program = applyMatchTransform(program);\n} catch (e) {\n  if (String(e.message).includes('@module selections at multiple paths')) {\n    reportDocumentError(documentName, 'Place @module selections inside fields with unique key arguments');\n  }\n  throw e;\n}","preventionTips":["Always nest @module fragments under a concrete field, never directly at fragment root.","Give each @module selection path a unique key argument.","Lint fragments for @module directives not preceded by a Field selection.","Split intentionally parallel module selections into separate fragments."],"tags":["relay-compiler","graphql","match-transform","module-fragment","validation","panic"],"backgroundTag":"invalid-module-selection-path","analyzedSha":"668b1b85e06261aa3b58dabfc51f8b5524a70955","analyzedAt":"2026-09-02T19:57:20.783Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}