`@defer` label uniqueness violation when fragment has `@argumentDefinitions` and is spread with different `@arguments` in the same query
- Dominant language
- Rust
- Stars
- 19k
- Forks
- 1.9k
- PR merge metrics
- No merged PRs in 30d
Description
Just a heads up that this prose is AI-generated, but I read it and it's accurate.
When a fragment uses `@argumentDefinitions` and is spread in multiple locations within the same query tree with different @arguments values, the Relay compiler generates multiple "variant" copies of the fragment in the persisted query output (e.g., `Details_pin` and `Details_pin_L7WGC`).
If the fragment body contains a `@defer` directive, both variants emit the same label value (derived from the original fragment name, not the variant name). This produces a persisted query with duplicate defer labels, which violates the https://spec.graphql.org/draft/#sec-Defer-And-Stream-Directive-Labels-Are-Unique that @defer/@stream labels must be unique within an operation.
Reproduction:
1. Define a fragment with `@argumentDefinitions` that contains a `@defer`:
```
fragment Details_pin on Pin
@argumentDefinitions(shouldShowSeoDrawerOption: { type: "Boolean!", defaultValue: false }) {
trackingParams
...ContextMenuMobile_pin @defer(if: $shouldDefer)
...PinCardDetails_pin @arguments(shouldShowSeoDrawerOption: $shouldShowSeoDrawerOption)
}
```
2. Spread this fragment in two locations with different @arguments:
```
# Location A (AuthMobile footer):
...Details_pin
# Location B (Unauth footer):
...Details_pin @arguments(shouldShowSeoDrawerOption: $shouldShowSeoDrawerOption)
```
3. Include both in a query that can reach both paths.
4. Run the Relay compiler and inspect the generated persisted query text.
Expected behavior:
The compiler should generate unique labels for each fragment variant, e.g.:
- `Details_pin$defer$ContextMenuMobile_pin`
- `Details_pin_L7WGC$defer$ContextMenuMobile_pin`
Or it should de-duplicate identical fragment bodies.
Actual behavior:
Both variants emit:
```
fragment Details_pin on Pin {
...ContextMenuMobile_pin @defer(label: "Details_pin$defer$ContextMenuMobile_pin", if: $shouldDefer)
}
fragment Details_pin_L7WGC on Pin {
...ContextMenuMobile_pin @defer(label: "Details_pin$defer$ContextMenuMobile_pin", if: $shouldDefer)
}
```
This causes server-side validation to reject the query: Defer/Stream directive label argument must be unique.
Impact:
Any fragment with both `@argumentDefinitions` and `@defer` that gets spread with different arguments in the same query will produce an invalid operation. In our case this affects 25+ queries.
-----
I don't think we're blocked, tbh. We can restructure things, but it's an annoying footgun and not obvious what's going on if you're new to GraphQL.
Contributor guide
Research direction
Start by reproducing the issue with the Relay compiler using the fragment and query shapes shown, then inspect the generated persisted query text. Done means variants either receive distinct @defer labels, or identical fragment bodies are de-duplicated, and the resulting operation passes server-side label uniqueness validation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100