Skip to content

Fix "Invalid state while adding deferred fragment node" when a @defer'd field errors#273

Merged
Cito merged 1 commit into
graphql-python:mainfrom
mixcloud:fix-defer-invalid-state-on-error
Jul 14, 2026
Merged

Fix "Invalid state while adding deferred fragment node" when a @defer'd field errors#273
Cito merged 1 commit into
graphql-python:mainfrom
mixcloud:fix-defer-invalid-state-on-error

Conversation

@ben-xo

@ben-xo ben-xo commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #271 (which is an issue we were seeing in production).

Problem

IncrementalGraph._add_deferred_fragment_node raises RuntimeError: Invalid state while adding deferred fragment node. while executing a spec-valid query, when two sibling top-level @defer fragments — each resolving a field that errors and each containing its own nested @defer — are executed with async resolvers.

We hit this in production (Strawberry over ASGI, streaming @defer as multipart/mixed) at ~5.7k occurrences / 48h.

Cause

When a deferred field errors, that fragment's execution group fails and the (parentless, root) deferred fragment is removed from _root_nodes. A sibling execution group completing afterwards then discovers its nested @defer and, in add_completed_successful_execution_group → _add_incremental_data_records → _add_deferred_fragment_node, recurses up to the now-removed parentless fragment — reaching the # pragma: no cover branch and raising.

Fix

Return instead of raising when the recursion reaches a parentless fragment that is no longer a root node: its subtree is no longer being delivered, so there is nothing to attach the record to. This mirrors the future.cancelled() guard in _enqueue ("defensive guard against a race with a stopping consumer").

Test

Adds a regression test in tests/execution/test_defer.py that reproduces the crash before this change (raising the exact RuntimeError) and passes after it. The full tests/execution/ suite passes.

The crash and its minimal trigger were discovered and reduced via fuzzing of @defer/@stream shapes with erroring async resolvers.

`IncrementalGraph._add_deferred_fragment_node` raised an internal
RuntimeError while executing a spec-valid query when two sibling top-level
`@defer` fragments — each resolving a field that errors and each containing
its own nested `@defer` — were executed with async resolvers.

When a deferred field errors, that fragment's execution group fails and
the (parentless, root) deferred fragment is removed from the root nodes. A
sibling execution group completing afterwards then discovers its nested
`@defer` and, in `_add_incremental_data_records`, recurses up to the
now-removed parentless fragment, reaching the `# pragma: no cover` branch
and raising.

Return instead of raising when reaching a parentless fragment that is no
longer a root node: its subtree is no longer being delivered, so there is
nothing to attach it to. This mirrors the `future.cancelled()` guard added
to `_enqueue` (a race with a stopping/removed consumer).

Adds a regression test that reproduces the crash prior to this change.

Fixes graphql-python#271
@ben-xo ben-xo force-pushed the fix-defer-invalid-state-on-error branch from a639d60 to 9be181c Compare July 13, 2026 15:15
@codspeed-hq

codspeed-hq Bot commented Jul 14, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 19 untouched benchmarks


Comparing mixcloud:fix-defer-invalid-state-on-error (9be181c) with main (56d4338)

Open in CodSpeed

@Cito

Cito commented Jul 14, 2026

Copy link
Copy Markdown
Member

Hi @ben-xo. Thanks a lot for the analysis and PR with regression test which all look solid to me at a first glance. Will have a deeper look later.

This issue occurs in a part of the code which has since been rewritten using a WorkQueue/IncrementalExecutor approach. I did not fully port this since it is heavily based on promises and hard to translate to the world of asyncio. However, this makes me think if I should still try to port it to Python somehow so that we stay aligned with upstream and avoid such problems.

@ben-xo

ben-xo commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

I noticed the build failed on mypy - double checked - I don't think it's my work, as the failures are in pre-existing files. (Just wanted to be diligent). Seems like there was a new release of mypy yesterday.

@ben-xo

ben-xo commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

I've raised #274 for the mypy errors against mypy 2.3.0 (unrelated to this PR, but hope it helps)

@Cito Cito merged commit eb76e07 into graphql-python:main Jul 14, 2026
9 of 10 checks passed
@Cito

Cito commented Jul 14, 2026

Copy link
Copy Markdown
Member

Yes, the mypy errors are caused by the mypy update. Will fix via your other. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incremental delivery: RuntimeError "Invalid state while adding deferred fragment node" when a @defer'd field errors

2 participants