facebook/relay · error
shadow_return_directive_fragment_name matched the directive
Error message
shadow_return_directive_fragment_name matched the directive
What it means
In transform_linked_field, shadow_return_directive_fragment_name(field) returned Some, meaning the field carries the shadow return directive, but the subsequent directives.named(SHADOW_RETURN_DIRECTIVE_NAME).expect(...) lookup found nothing. The two lookups should be consistent — they both key on the same directive — so a mismatch indicates duplicate/inconsistent directive name constants or a race where directives were mutated between checks.
Source
Thrown at compiler/crates/relay-transforms/src/relay_resolvers/shadow_transform.rs:385
}
fn transform_linked_field(
&mut self,
field: &graphql_ir::LinkedField,
) -> Transformed<graphql_ir::Selection> {
// Check for RelayResolverFieldMetadata attached by field_transform
if let Some(field_metadata) = RelayResolverFieldMetadata::find(&field.directives) {
self.validate_resolver_metadata(field_metadata, field.definition.item);
}
// Convert the schema-known `@__relay_shadow_return` syntax directive into
// the typed associated-data marker and strip the syntax directive so it
// never reaches codegen.
if let Some(return_fragment_name) = shadow_return_directive_fragment_name(field) {
let shadow_return_directive = field
.directives
.named(*SHADOW_RETURN_DIRECTIVE_NAME)
.expect("shadow_return_directive_fragment_name matched the directive");
let marker = ShadowReturnMarker {
return_fragment_name,
spread_location: shadow_return_directive.location,
};
let mut directives: Vec<_> = field
.directives
.iter()
.filter(|directive| directive.name.item != *SHADOW_RETURN_DIRECTIVE_NAME)
.cloned()
.collect();
directives.push(marker.into());
let selections = self
.transform_selections(&field.selections)
.replace_or_else(|| field.selections.clone());
return Transformed::Replace(Selection::LinkedField(Arc::new(LinkedField {
directives,
selections,
..field.clone()View on GitHub (pinned to 668b1b85e0)
Solutions
- Verify shadow_return_directive_fragment_name and the expect() use the identical directive-name constant; align them.
- Re-run from a clean build so no stale incremental transform output mutates directives.
- Audit other transforms for anything that strips directives named like the shadow return marker and reorder passes.
- Use Option combinators instead of expect to surface a diagnostic message if you control the fork.
Example fix
// before
.expect("shadow_return_directive_fragment_name matched the directive")
// after
let shadow_return_directive = field.directives.named(*SHADOW_RETURN_DIRECTIVE_NAME)
.unwrap_or_else(|| panic!("field {:?} reported shadow-return fragment {:?} but directive missing", field.name, return_fragment_name)); Defensive patterns
Strategy: validation
Validate before calling
// Confirm directive presence with one constant before transforming:
let directive_name = *SHADOW_RETURN_DIRECTIVE_NAME;
if shadow_return_directive_fragment_name(field).is_some() {
assert!(field.directives.named(directive_name).is_some(),
"field {} claims shadow-return fragment but lacks the directive", field.name.item.0);
} Type guard
fn has_shadow_return_directive(field: &LinkedField) -> bool {
shadow_return_directive_fragment_name(field).is_some()
&& field.directives.named(*SHADOW_RETURN_DIRECTIVE_NAME).is_some()
} Try / catch
// Prefer graceful handling over expect when forking:
match field.directives.named(*SHADOW_RETURN_DIRECTIVE_NAME) {
Some(d) => { /* build ShadowReturnMarker */ }
None => diagnostics.add(Diagnostic::error(field.location, "shadow return marker directive missing")),
} Prevention
- Use a single shared constant for the directive name everywhere — never re-declare it in helper functions
- Rebuild cleanly after pulling Relay updates to avoid mixed transform versions
- Audit any custom transform that rewrites field.directives for accidental stripping
- Add a unit test pairing shadow_return_directive_fragment_name with directive lookup
When it happens
Trigger: A field for which shadow_return_directive_fragment_name matches (via the return-fragment directive name) but directives.named with the separate SHADOW_RETURN_DIRECTIVE_NAME constant finds no match — e.g. mismatched directive name constants after a refactor, or a parallel transform stripped the directive between the two calls.
Common situations: Custom or forked Relay builds where directive name constants were edited inconsistently; transform pipelines inserting an extra pass that rewrites/removes field directives; copy-pasted helper that checks a different directive name than the one fetched.
Related errors
- has_root_fragment() returned true
- Expected parent type
- Expected field to be defined on concrete type
- Previous validation passes ensured this exists.
- @__metadata directive should have only one argument!
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/bd262b5fc6338a2f.
Report an issue: GitHub.