Skip to content

@include(if: false) ignored on FragmentSpread when @defer is also present #266

Description

@06terlan

Description

When @include(if: false) and @defer are both on the same fragment spread, the @include directive is ignored and the deferred fragment is still scheduled and delivered.

Reproduction

 query DeferSpreadIncludeFalse {                                                                                                                                          
    user {                                                                                                                                                                 
      id                                                                                                                                                                   
      ...UserExtras @include(if: false) @defer(label: "userExtras")                                                                                                        
    }                                                                                                                                                                      
  }                                                                                                                                                                        
                                                                                                                                                                           
  fragment UserExtras on User {                                                                                                                                            
    username                                                                                                                                                               
    fullName                                                                                                                                                               
  }

Expected: Fragment excluded entirely, no deferred payload.
Actual: Fragment is deferred and delivered:

{"data":{"user":{"id":"1"}},"hasNext":true,"pending":[{"id":"0","path":["user"],"label":"userExtras"}]}
{"hasNext":false,"incremental":[{"data":{"username":"test","fullName":"Test"},"id":"0"}],"completed":[{"id":"0"}]}

Root Cause

In collect_fields_impl() (graphql/execution/collect_fields.py), for FragmentSpreadNode:                                                                                  

  new_defer_usage = get_defer_usage(...)          # evaluated first                  

  if new_defer_usage is None and (                # @include only checked if NO @defer                                                                                     
      frag_name in visited_fragment_names 
      or not should_include_node(variable_values, selection)                         
  ):                                      
      continue                            

  should_include_node() is gated behind new_defer_usage is None. When @defer is present, new_defer_usage is not None, so the inclusion check is skipped entirely.          

  Compare with InlineFragmentNode handling (lines 199-202), where should_include_node() is checked before get_defer_usage() — that path works correctly.

Suggested Fix

  Check should_include_node() unconditionally before evaluating @defer:  

  elif isinstance(selection, FragmentSpreadNode):                        
      frag_name = selection.name.value    
                                                                         
      if not should_include_node(variable_values, selection):                        
          continue                        

      new_defer_usage = get_defer_usage(  
          operation, variable_values, selection, defer_usage                         
      )                                   

      if new_defer_usage is None and frag_name in visited_fragment_names:            
          continue                        

Version

graphql-core 3.3.0a11

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions