facebook/relay · error
Expected filters_arg to have been previously validated.
Error message
Expected filters_arg to have been previously validated.
What it means
This is an internal invariant panic in Relay's HandleFieldTransform. The filters argument of a @connection/@handle directive must be a list of string literals; an earlier validation pass guarantees this, so when extract_values_from_handle_field_directive_helper encounters a list element that is not a String literal (or the whole value is neither a List nor Null), it panics with this message instead of emitting a friendly diagnostic.
Source
Thrown at compiler/crates/relay-transforms/src/handle_fields/handle_field_util.rs:235
// validated first as part of validate_connections validation step.
let key = match key_arg {
Some((_, value)) => value
.get_string_literal()
.expect("Expected key_arg to have been previously validated."),
None => "".intern(),
};
let handle= match handler_arg {
Some((_, value)) => value.get_string_literal().expect("Expected handler_arg to have been previously validated."),
None => default_handler.expect("Expected handler_arg to have been previously validated or a default to have been provided."),
};
let filters = match filters_arg {
Some((_, value)) => match value {
ConstantValue::List(list_val) => Some(
list_val
.iter()
.map(|val| {
val.get_string_literal()
.expect("Expected filters_arg to have been previously validated.")
})
.collect::<Vec<_>>(),
),
ConstantValue::Null() => None,
_ => unreachable!("Expected filters_arg to have been previously validated.",),
},
None => default_filters,
};
let dynamic_key = match dynamic_key_arg {
Some((_, value)) => match value {
Value::Variable(_) => Some(value.clone()),
_ => unreachable!("Expected dynamic_key_arg to have been previously validated."),
},
None => None,
};
let handle_args = handle_args_arg.map(|arg| {
if let Value::Object(args) = &arg.value.item {
args.clone()View on GitHub (pinned to 668b1b85e0)
Solutions
- Ensure every value inside the filters argument of @connection/@handle directives is a quoted string literal, e.g. filters: ["isAdmin"].
- Find the custom transform or code path that constructed the directive and route it through the standard validation pass before the handle field transform.
- If generating IR programmatically, emit only Value::Constant(ConstantValue::String(..)) for filter items.
- Align all relay compiler crates/dependencies to the same version so validation and transforms agree.
- If the document looks valid, minimize it and file a relay-compiler issue — this is meant to be unreachable.
Example fix
// before
fragment F on Query {
users(first: 10) @connection(key: "F_users", filters: [2]) { ... }
}
// after
fragment F on Query {
users(first: 10) @connection(key: "F_users", filters: ["isAdmin"]) { ... }
} Defensive patterns
Strategy: validation
Validate before calling
function validateFiltersArg(directive) {
const filters = directive.arguments?.find(a => a.name === 'filters');
if (!filters) return true; // filters is optional
if (filters.value.kind !== 'ListValue') return false;
return filters.value.values.every(
v => v.kind === 'StringValue' || v.kind === 'NullValue'
);
}
// before compiling:
// if (!validateFiltersArg(directive)) throw new Error('filters must be string literals'); Type guard
function isStringLiteralList(v) {
return v != null &&
v.kind === 'ListValue' &&
v.values.every(x => x.kind === 'StringValue');
} Try / catch
try {
const values = extractValuesFromHandleFieldDirective(directive);
} catch (e) {
if (String(e.message).includes('filters_arg')) {
reportCompilerBug(directive.loc, 'filters argument contains non-string literals');
}
throw e;
} Prevention
- Only use quoted string literals in filters: ["..."] of @connection/@handle directives.
- Never construct handle directive IR manually without running the standard validation pass.
- Keep all relay-compiler crates at the same version.
- Lint GraphQL documents for filters arguments containing numeric or enum values.
When it happens
Trigger: Calling extract_values_from_handle_field_directive (via build_handle_field_directive_from_connection_directive) on a @connection(handle:...) or @handle directive whose filters list contains non-string-literal items — e.g. an Int, Boolean, Enum literal, or a nested list — meaning the earlier validation did not run or was bypassed.
Common situations: Hand-writing or programmatically generating IR that skips the validation visitor; custom transforms that build handle directives directly; a Relay compiler version mismatch where a new constant value kind reaches the util unvalidated; reusing stale IR documents across compiler versions.
Related errors
- expected to find a supported argument as checked before
- unexpected value for @defer if argument: {other:?}
- unexpected value for @stream if argument: {other:?}
- Unexpected custom directives: {:#?}
- Expected a scalar field.
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/beb5cefde67f6b02.
Report an issue: GitHub.