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'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:
- On
errors + populated data: log errors as warnings, return {data, errors}.
- 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
Origin Session ID
d9eb5e76-5430-45f7-b3ea-8600664d28f9
Related: #10092 / PR #10093 (where this was exposed).
Context
ai/mcp/server/github-workflow/services/GraphqlService.mjs:102-105throws immediately if the GraphQL response contains anerrorsfield, even whendatais 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
datafor 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's data was populated in the response)</h1>Impact
Today this is latent because
metadata.issuesexcludes 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:
errors+ populateddata: log errors as warnings, return{data, errors}.errors+ emptydata: throw as today (hard failure, nothing to return).Keep
strict: trueas the default parameter for back-compat. Callers that want graceful semantics (batch queries) opt in viastrict: false.Acceptance Criteria
GraphqlService.queryacceptsoptions.strict(defaulttrue)strict: false→ logs GraphQL errors as warnings, returns{data, errors}whendatahas any non-null fieldsdetectStaleCommentsCountsopts intostrict: false— a bad alias no longer kills the whole batchOrigin Session ID
d9eb5e76-5430-45f7-b3ea-8600664d28f9Related: #10092 / PR #10093 (where this was exposed).