LearnNewsExamplesServices
Frontmatter
id10096
title[bug] GraphqlService.query discards partial data when GraphQL returns errors — breaks aliased batch queries
stateClosed
labels
bugaiarchitecture
assigneesneo-gpt
createdAtApr 19, 2026, 2:20 PM
updatedAtJun 3, 2026, 11:22 AM
githubUrlhttps://github.com/neomjs/neo/issues/10096
authortobiu
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 3, 2026, 11:22 AM

[bug] GraphqlService.query discards partial data when GraphQL returns errors — breaks aliased batch queries

Closed v13.0.0/archive-v13-0-0-chunk-4 bugaiarchitecture
tobiu
tobiu commented on Apr 19, 2026, 2:20 PM

Context

ai/mcp/server/github-workflow/services/GraphqlService.mjs:102-105 throws immediately if the GraphQL response contains an errors field, even when data is partially populated:

if (json.errors) {
    logger.error('GitHub API returned errors:', json.errors);
    throw new Error(`GitHub API error: ${json.errors.map(e => e.message).join(', ')}`);
}

GraphQL's spec explicitly allows partial data alongside per-field errors. When an aliased batch query asks for N issues and one alias happens to reference a PR instead of an Issue, GitHub returns:

  • data.repository.issue42: { ... valid ... }
  • data.repository.issue43: null (the bad one)
  • errors: [{"message": "Could not resolve to an Issue with the number of 43", ...}]

Our service discards the populated data for the other 99 aliases and throws. Downstream: the entire batch fails, even though 99 issues were successfully resolved.

Reproducer

Discovered during PR #10093 verification:

node --input-type=module -e "
import {GH_Config} from './ai/services.mjs';
import GraphqlService from './ai/mcp/server/github-workflow/services/GraphqlService.mjs';
import {buildIssueTotalsBatchQuery} from './ai/mcp/server/github-workflow/services/queries/issueQueries.mjs';
const q = buildIssueTotalsBatchQuery([10090, 10091]); // 10091 is a PR
await GraphqlService.query(q, {owner:'neomjs', repo:'neo'}, true);
"
<h1 class="neo-h1" data-record-id="4">Error: GitHub API error: Could not resolve to an Issue with the number of 10091.</h1>

<h1 class="neo-h1" data-record-id="5">(even though 10090&#39;s data was populated in the response)</h1>

Impact

Today this is latent because metadata.issues excludes PRs by contract. But every future aliased-batch query inherits this fragility — a single stale/converted reference will blackhole a whole batch of legitimate lookups. detectStaleCommentsCounts (introduced in #10092 / PR #10093) is one such consumer; more will follow as the InvalidationDetector pattern (#10094 if filed) expands.

The Ask

Soften error handling to honor GraphQL's partial-data semantics:

  1. On errors + populated data: log errors as warnings, return {data, errors}.
  2. On errors + empty data: throw as today (hard failure, nothing to return).

Keep strict: true as the default parameter for back-compat. Callers that want graceful semantics (batch queries) opt in via strict: false.

Acceptance Criteria

  • GraphqlService.query accepts options.strict (default true)
  • strict: false → logs GraphQL errors as warnings, returns {data, errors} when data has any non-null fields
  • detectStaleCommentsCounts opts into strict: false — a bad alias no longer kills the whole batch
  • Playwright spec for both modes: strict throws on any error; non-strict returns populated data + errors array
  • JSDoc explains the strict/non-strict semantics and when to pick each

Origin Session ID

d9eb5e76-5430-45f7-b3ea-8600664d28f9

Related: #10092 / PR #10093 (where this was exposed).

tobiu added the bug label on Apr 19, 2026, 2:20 PM
tobiu added the ai label on Apr 19, 2026, 2:20 PM
tobiu added the architecture label on Apr 19, 2026, 2:20 PM
tobiu cross-referenced by PR #10111 on Apr 19, 2026, 11:53 PM
tobiu referenced in commit a56a444 - "fix(ai): preserve GraphQL partial data in non-strict mode (#10096) (#12400) on Jun 3, 2026, 11:22 AM
tobiu closed this issue on Jun 3, 2026, 11:22 AM