facebook/relay · error

Expected a key

Error message

Expected a key

What it means

Primitive::assert_key unwraps a Primitive enum expecting the Key variant. The relay-codegen AST can wrap either a scalar value or a client-object key; calling assert_key on a Primitive that is String/Boolean/Null/etc. panics because no key exists to return.

Source

Thrown at compiler/crates/relay-codegen/src/ast.rs:146

        resolver_fn: Box<Primitive>,
        injected_field_name_details: Option<(StringKey, bool)>,
    },
}

impl Primitive {
    pub fn assert_string(&self) -> StringKey {
        if let Primitive::String(key) = self {
            *key
        } else {
            panic!("Expected a string");
        }
    }

    pub fn assert_key(&self) -> AstKey {
        if let Primitive::Key(key) = self {
            *key
        } else {
            panic!("Expected a key");
        }
    }

    pub fn string_or_null(str: Option<StringKey>) -> Primitive {
        match str {
            None => Primitive::Null,
            Some(str) => Primitive::String(str),
        }
    }
}

#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
pub struct AstKey(usize);

impl AstKey {
    pub fn as_usize(self) -> usize {
        self.0
    }

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Check the Primitive variant with matches! or a match arm before calling assert_key, and handle non-key variants gracefully.
  2. Trace why the AST node holds a non-key value: verify the field/fragment is actually a client edge (@refetchable / @catch) so codegen emits a Key.
  3. Fix the calling codegen pass to call the correct accessor (e.g. assert_string) for the actual variant.
  4. Pin/upgrade relay compiler so the pass producing the Primitive matches the pass consuming it.

Example fix

// before
let key = primitive.assert_key();
// after
let key = match primitive {
    Primitive::Key(key) => key,
    other => panic!("expected key, got {:?}", other),
};
Defensive patterns

Strategy: type-guard

Validate before calling

if matches!(primitive, Primitive::Key(_)) { /* safe to assert_key */ }

Type guard

fn is_key(p: &Primitive) -> bool { matches!(p, Primitive::Key(_)) }

Try / catch

// Rust panics are not catchable per-call; wrap the whole codegen step
let result = std::panic::catch_unwind(|| primitive.assert_key());
match result {
    Ok(key) => use_key(key),
    Err(_) => fallback_to_non_key_handling(),
}

Prevention

When it happens

Trigger: Codegen logic calls primitive.assert_key() on a Primitive produced by Primitive::string/string_or_null/boolean rather than from a key-valued AST node — e.g. reading a field whose value was generated as a plain string literal.

Common situations: Custom codegen plugins/extensions walking the generated AST and assuming every primitive is a key; a schema/config change causing a field that used to be @refetchable/client-edge linked to become a plain string, so the primitive variant changed.

Related errors


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