facebook/relay · error
Unexpected PropertyAccessor
Error message
Unexpected PropertyAccessor
What it means
Primitive::PropertyAccessor is used when printing values that must be read from runtime variables (e.g. `$variables.key` accessors) in generated code paths that support it. write_constant_value only emits static constants, so a PropertyAccessor appearing there means a runtime-dependent value leaked into a constant position and the printer panics instead of emitting invalid literal code.
Source
Thrown at compiler/crates/relay-codegen/src/printer.rs:935
}
if !obj.is_empty() {
f.pop();
}
f.push('}');
Ok(())
}
}
}
Primitive::Null | Primitive::SkippableNull => {
f.push_str("null");
Ok(())
}
Primitive::StorageKey(_, _) => panic!("Unexpected StorageKey"),
Primitive::RawString(_) => panic!("Unexpected RawString"),
Primitive::GraphQLModuleDependency(_) => panic!("Unexpected GraphQLModuleDependency"),
Primitive::JSModuleDependency { .. } => panic!("Unexpected JSModuleDependency"),
Primitive::ResolverModuleReference { .. } => panic!("Unexpected ResolverModuleReference"),
Primitive::PropertyAccessor(_) => panic!("Unexpected PropertyAccessor"),
Primitive::DynamicImport { .. } => panic!("Unexpected DynamicImport"),
Primitive::RelayResolverModel { .. } => panic!("Unexpected RelayResolver"),
}
}
View on GitHub (pinned to 668b1b85e0)
Solutions
- Remove variables from argument positions that must be constant (e.g. @static_arg values); use literal values and pass dynamic data at execution time
- Ensure the variable-containing argument is printed via the variables-handling path (which filters Variables before constant printing) rather than nested inside constant values
- Inspect the custom transforms for mislabeled dynamic values and emit PropertyAccessor only in accessor-supported writers
- Upgrade the Relay compiler so variable filtering matches the printer's expectations
Example fix
// before ...Frag @static_arg(key: "id", value: $dynamicId) // after ...Frag @static_arg(key: "id", value: "static-id") // pass dynamicId at runtime instead
Defensive patterns
Strategy: validation
Validate before calling
function assertStaticStaticArgs(doc) {
visit(doc, {
Directive(node) {
if (node.name.value === 'static_arg') {
node.arguments?.forEach(a => {
if (containsVariable(a.value)) {
throw new Error(`@static_arg value for '${a.name.value}' must not contain variables`);
}
});
}
}
});
} Type guard
const isStaticValue = (v) => v.kind !== 'Variable' && !(v.values ?? []).some(isStaticValue === undefined ? false : containsVar(v));
Try / catch
try {
generateArtifacts();
} catch (e) {
if (String(e).includes('Unexpected PropertyAccessor')) {
// replace variable-dependent values in constant positions with literals
}
throw e;
} Prevention
- Never use variables in @static_arg or other constant-required positions
- Pass dynamic values via regular query variables at execution time
- Verify custom transforms emit PropertyAccessor only in accessor-supported writers
- Run full codegen on all documents in CI
When it happens
Trigger: A value dependent on query variables (yielding a PropertyAccessor primitive, typically after Variables-in-argument handling) reaching write_constant_value — e.g. variable-derived arguments printed in a context that only supports constants, such as nested constant printing in write_argument_value.
Common situations: Arguments containing variables in positions codegen expects to be fully static (e.g. @static_arg values, storage-key embedded values with nested variables); custom transforms mislabeling dynamic values; compiler upgrades changing which positions accept variables.
Related errors
- Unexpected StorageKey
- Unexpected RawString
- Unexpected GraphQLModuleDependency
- Unexpected JSModuleDependency
- Unexpected ResolverModuleReference
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/a0c1ce4399356a06.
Report an issue: GitHub.