facebook/relay · error
Expected at most one handle directive, got `{handle_field_di
Error message
Expected at most one handle directive, got `{handle_field_directive:?}` and `{other_handle_field_directive:?}`. What it means
In Reader codegen, a field may carry at most one @handle (handleField) directive. build_field_name_and_alias collects handle directives with an iterator and panics if a second one exists, because codegen cannot merge multiple handle aliases into one reader field.
Source
Thrown at compiler/crates/relay-codegen/src/build_ast.rs:1463
});
}
};
result.push(Primitive::Key(self.object(object)))
}
}
fn build_field_name_and_alias(
&self,
mut name: StringKey,
alias: Option<WithLocation<StringKey>>,
directives: &[Directive],
) -> (StringKey, Option<StringKey>) {
let mut alias = alias.map(|alias| alias.item);
if self.variant == CodegenVariant::Reader {
let mut handle_field_directives = extract_handle_field_directives(directives);
if let Some(handle_field_directive) = handle_field_directives.next() {
if let Some(other_handle_field_directive) = handle_field_directives.next() {
panic!(
"Expected at most one handle directive, got `{handle_field_directive:?}` and `{other_handle_field_directive:?}`."
);
}
let values = extract_values_from_handle_field_directive(handle_field_directive);
alias = alias.or(Some(name));
name = if values.key == CODEGEN_CONSTANTS.default_handle_key {
format!("__{}_{}", name, values.handle).intern()
} else {
format!("__{}_{}", values.key, values.handle).intern()
}
}
}
(name, alias)
}
fn build_fragment_spread(&mut self, frag_spread: &FragmentSpread) -> Primitive {
if let Some(no_inline_metadata) =
NoInlineFragmentSpreadMetadata::find(&frag_spread.directives)View on GitHub (pinned to 668b1b85e0)
Solutions
- Keep only one @handle directive on the field; delete the duplicate.
- If you need multiple handles, split the field into aliased duplicates, one per @handle, e.g. `me @handle(key: "")` and `me2: me @handle(key: "other")`.
- Check git history/merge conflicts for accidentally duplicated directive lines.
- Verify with a schema/docs check that handlers like connection or @appendEdge only need a single handle per field.
Example fix
// before
viewer @handle(key: "connection_viewer") @handle(key: "other_viewer") { id }
// after
viewer @handle(key: "connection_viewer") { id }
Defensive patterns
Strategy: validation
Validate before calling
fn field_has_single_handle(field: &Field) -> Result<(), String> {
let count = field.directives.iter().filter(|d| d.name.item.0 == "handle").count();
if count > 1 { Err(format!("field {} has {} @handle directives", field.name.item.0, count)) }
else { Ok(()) }
}
Try / catch
let result = std::panic::catch_unwind(|| build_scalar_field(...));
if result.is_err() { report("field has more than one @handle directive"); }
Prevention
- Allow only one @handle per field; use aliased duplicate fields for multiple handles.
- Add a GraphQL lint rule rejecting multiple @handle directives on one field.
- Check merge diffs for duplicated directive lines.
When it happens
Trigger: A scalar or linked field in a fragment annotated with more than one @handle directive (e.g. `@handle(key: "") @handle(key: "")`) compiled with CodegenVariant::Reader.
Common situations: Adding a second @handle for a different connection handler and forgetting the compiler allows only one per field; merge conflicts that keep two @handle lines; copying examples with multiple handles onto a single field.
Related errors
- The {} argument in exec_time_resolvers directive should be t
- @__metadata directive should have only one argument!
- @__metadata directive expect only constant argument values.
- Unexpected RelayResolverMetadata on inline fragment while ge
- Unexpected parent type for resolver.
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/36ed251082369729.
Report an issue: GitHub.