Sibling errors should not be added after propagation
At a glance​
- Identifier: #1184
- Stage: RFC0: Strawman
- Champion: @benjie
- PR: Sibling errors should not be added after propagation
Timeline​
- Spec PR created on 2025-07-10 by benjie
- Commit pushed: Sibling errors should not be added after propagation on 2025-07-10 by @benjie
This PR is built on top of:
- #1183
GraphQL.js output is not (currently) stable after an operation terminates: more errors may be added to the result after the promise has resolved!
Reproduction with
graphql
moduletest.mts
import type { ExecutionResult } from "graphql";
import {
graphql,
GraphQLInt,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
} from "graphql";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const Test = new GraphQLObjectType({
name: "Test",
fields: {
a: {
type: GraphQLInt,
async resolve() {
await sleep(0);
throw new Error(`a`);
},
},
b: {
type: new GraphQLNonNull(GraphQLInt),
async resolve() {
await sleep(10);
throw new Error(`b`);
},
},
c: {
type: GraphQLInt,
async resolve() {
await sleep(20);
throw new Error(`c`);
},
},
},
});
const Query = new GraphQLObjectType({
name: "Query",
fields: {
test: {
type: Test,
resolve() {
return {};
},
},
},
});
const schema = new GraphQLSchema({
query: Query,
});
const result = await graphql({
schema,
source: `{ test { a b c } }`,
});
console.log("Result:");
console.log();
console.log(JSON.stringify(result, null, 2));
await sleep(100);
console.log();
console.log("Exact same object 100ms later:");
console.log();
console.log(JSON.stringify(result, null, 2));$ node test.mts
Result:
{
"errors": [
{ "message": "a", "path": ["test", "a"] },
{ "message": "b", "path": ["test", "b"] }
],
"data": { "test": null }
}
Exact same object 100ms later:
{
"errors": [
{ "message": "a", "path": ["test", "a"] },
{ "message": "b", "path": ["test", "b"] },
{ "message": "c", "path": ["test", "c"] }
],
"data": { "test": null }
}(I've formatted this output for brevity)
The reason for this: though we note in the spec that you may cancel sibling execution positions, we don't do that in GraphQL.js; and furthermore, we even process errors from the result and add them to the errors list!
This is particularly problematic for client-side "throw on error". Given this schema:
type Query {
test: Test
}
type Test {
a: Int # Throws immediately
b: Int! # Throws after 10ms
c: Int # Throws after 20ms
}And the same spec-valid result as above:
{
"errors": [
{ "message": "a", "path": ["test", "a"] },
{ "message": "b", "path": ["test", "b"] },
{ "message": "c", "path": ["test", "c"] }
],
"data": { "test": null }
}Technically the
Test.b
field is the field that causeddata.test
to be null - it's non-nullable, so it triggered error propagation - but without looking at the schema we can't determine this.Solution: recommend that servers don't keep adding to
errors
after error propagation has occurred. This would mean:
- GraphQL.js won't keep adding to
errors
after the operation has "completed"- We can throw the last error received that relates to the associated field, and trust that for an implementation following the recommendations it's going to be the one either from the field itself or from the field that triggered error propagation to this level.