Skip to content

[graphql-pro] @defer incremental delivery: wrong path when sibling fields exist at same level (GraphQL::Pro::Defer) #5595

@robinvrd

Description

@robinvrd

Note: this involves GraphQL::Pro::Defer — happy to follow up through pro support channels if preferred.


Describe the bug

When a query uses @defer on a field and a sibling field exists at the same parent level (e.g. mcpServers alongside environment), the incremental delivery chunks contain the wrong path. The deferred data is delivered with the path of a sibling's node instead of the correct path, causing clients to apply the patch to the wrong location.

Versions

graphql version: 2.5.13
graphql-pro version: 1.29.13
rails: 8.0.4.1

GraphQL schema

class ApplicationSchema < GraphQL::Schema
  use GraphQL::Pro::Defer
  use GraphQL::Pro::FutureStream
end

# Controller
class GraphQLController < ApplicationController
  include ActionController::Live

  def execute
    result = ApplicationSchema.execute(query, variables:, context:, operation_name:)
    (deferred = result.context[:defer]) ? render_stream(deferred) : render(json: result)
  end

  def render_stream(deferred)
    response.headers['Last-Modified'] = Time.now.httpdate
    deferred.stream_http_multipart(response, incremental: true)
  end
end

# Relevant types (simplified)
class OrganizationType < GraphQL::Schema::Object
  field :environment, EnvironmentType, null: true
  field :mcp_servers, MCPServerType.connection_type, null: false
end

class EnvironmentType < GraphQL::Schema::Object
  field :connections, ConnectionType.connection_type, null: false
end

class ConnectionType < GraphQL::Schema::Object
  field :connected, Boolean, null: false
end

class MCPServerType < GraphQL::Schema::Object
  field :task_counts, TaskStatusCountsType, null: false
end

GraphQL query

query ConnectionsQuery($organizationId: ID!, $environmentSlug: String!) {
  organization(id: $organizationId) {
    id
    environment(slug: $environmentSlug) {
      id
      connections(first: 10) {
        nodes {
          id
          ... @defer {
            connected
          }
        }
      }
    }
    mcpServers(first: 10) {
      nodes {
        id
        taskCounts {
          total
        }
      }
    }
  }
}

Steps to reproduce

  1. Set up a schema with use GraphQL::Pro::Defer and a controller using stream_http_multipart(response, incremental: true)
  2. Define a query type with two sibling fields at the same level — one containing a @defer fragment, the other with regular nested fields
  3. Execute the query and inspect the raw multipart HTTP response chunks

Expected behavior

Incremental chunks should carry the correct path pointing to the deferred selection set inside environment.connections.nodes:

{ "incremental": [{ "path": ["organization", "environment", "connections", "nodes", 0], "data": { "connected": true } }], "hasNext": true }
{ "incremental": [{ "path": ["organization", "environment", "connections", "nodes", 1], "data": { "connected": true } }], "hasNext": false }

Actual behavior

Both incremental chunks carry a path pointing to the sibling field (mcpServers) instead of environment.connections, and both incorrectly use index 0:

Chunk 1 (initial) — correct:

{
  "hasNext": true,
  "data": {
    "organization": {
      "environment": {
        "connections": { "nodes": [{ "id": "aca6e0bf-..." }, { "id": "ed1b3e5e-..." }] }
      },
      "mcpServers": {
        "nodes": [{ "id": "019c4c22-...", "taskCounts": { "total": 0 } }]
      }
    }
  }
}

Chunk 2 — ❌ wrong path, wrong field, wrong index:

{ "incremental": [{ "path": ["organization", "mcpServers", "nodes", 0, "__typename"], "data": { "connected": true } }], "hasNext": true }

Chunk 3 — ❌ wrong path, wrong field, same index 0 for both items:

{ "incremental": [{ "path": ["organization", "mcpServers", "nodes", 0, "__typename"], "data": { "connected": true } }], "hasNext": false }

Two distinct issues in the paths:

  1. Wrong field: points to mcpServers instead of environment.connections
  2. Wrong index: both chunks use index 0 instead of 0 and 1

Additional context

  • Removing taskCounts from mcpServers (or removing mcpServers entirely) produces correct paths — the bug only manifests when a sibling field with nested selections exists alongside the @defer-containing field at the same parent level.
  • The query validates fine; the schema is otherwise functioning correctly.
  • Workaround: remove @defer from the query or split into two separate queries.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions