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

  1. Verify shadow_return_directive_fragment_name and the expect() use the identical directive-name constant; align them.
  2. Re-run from a clean build so no stale incremental transform output mutates directives.
  3. Audit other transforms for anything that strips directives named like the shadow return marker and reorder passes.
  4. 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

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


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/bd262b5fc6338a2f. Report an issue: GitHub.