Skip to content

GraphQl: Prevent infinite query loop on unexpected responses - #355

Open
BigRoy wants to merge 7 commits into
developfrom
bugfix/graphql-infinite-pagination-loop
Open

BigRoy wants to merge 7 commits into
developfrom
bugfix/graphql-infinite-pagination-loop

Conversation

@BigRoy

@BigRoy BigRoy commented Sep 12, 2026

Copy link
Copy Markdown
Member

Bug

Two unexpected (but valid-JSON) GraphQl responses make GraphQlQuery.query() / continuous_query() loop forever, sending the same request to the server again and again:

  1. A response without errors where data is null (or missing).
  2. A page reporting hasNextPage: true without an endCursor (e.g. an empty page).

Neither is produced by a healthy server in normal use, but when it happens the client hangs (and hammers the server) instead of failing with an error that can be debugged.

Cause

  1. parse_result(None, ...) returns immediately, so no field changes its need_query state and the while self.need_query loop repeats the identical query.
  2. The cursor was set to None, which means "start from the beginning", so pagination restarted from the first page. (On the very first page it instead raised the misleading "Cursor didn't change" error.)

Fix

  1. Query sending is moved into one _query_data helper (it was copy-pasted three times). It raises GraphQlQueryError including the response, query and variables when data is missing.
  2. When another page is reported without a cursor, pagination of that field stops with a warning containing the field path and number of received items.

Reproduce

Not reproducible against a healthy server. tests/test_graphql_infinite_loop.py uses a scripted fake connection returning these responses; on develop the fake connection raises "Infinite query loop" after 10 identical requests.

Testing notes

  • Run pytest tests/test_graphql_infinite_loop.py.
  • Regular queries are unaffected, e.g. get_folders, get_versions, get_events return the same results as before.

🤖 Generated with Claude Code

- Response with 'data: null' did not change pagination state, so the
  same query was sent again forever. Raise GraphQlQueryError with the
  query instead.
- Page reporting another page without a cursor reset the cursor to
  'None', so pagination started again from the first page. Stop
  pagination of the field with a warning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread ayon_api/graphql.py Outdated
Comment thread ayon_api/graphql.py Outdated
Comment thread ayon_api/graphql.py Outdated
Comment thread ayon_api/graphql.py Outdated
Comment thread ayon_api/graphql.py Outdated
Comment thread ayon_api/graphql.py Outdated
BigRoy and others added 2 commits September 14, 2026 19:37
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
@BigRoy BigRoy self-assigned this Sep 14, 2026
@BigRoy
BigRoy requested review from iLLiCiTiT and a lite review from Copilot and removed request for iLLiCiTiT September 14, 2026 17:39
@BigRoy BigRoy added the type: bug Something isn't working label Sep 14, 2026
@BigRoy
BigRoy marked this pull request as ready for review September 14, 2026 17:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Resolve the empty-data loop and missing-cursor pagination failure.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This pull request hardens GraphQL query handling against unexpected responses that could cause infinite request or pagination loops.

Changes:

  • Centralizes query execution and validates response data.
  • Updates pagination handling for missing cursors.
  • Adds regression tests for malformed responses.
File summaries
File Summary Final review findings
tests/test_graphql_infinite_loop.py Adds regression coverage for malformed responses and pagination termination. None.
ayon_api/graphql.py Implements shared response handling and pagination safeguards. Critical (3 votes, line 365): Empty data mappings can still loop; reject them as invalid. Moderate (3 votes, line 948): Missing-cursor pagination raises instead of stopping with a warning while preserving parsed output.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ayon_api/graphql.py
raise GraphQlQueryFailed(response.errors, query_str, variables)

data = response.data.get("data")
if data is None:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iLLiCiTiT thoughts? Might make sense?

Comment thread ayon_api/graphql.py
Comment thread tests/test_graphql_infinite_loop.py Outdated
Comment thread tests/test_graphql_infinite_loop.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants