fix(graphqlSchema): surface schema-load errors instead of a 30s timeout - #1918
fix(graphqlSchema): surface schema-load errors instead of a 30s timeout#1918kylebernhardy wants to merge 1 commit into
Conversation
…ut (#1917) When a graphqlSchema component's initial load throws (bad directive, reserved database name, malformed schema), handleApplication awaited only the success event and hung until the component-load watchdog fired 30s later with a generic "handleApplication timed out" message that named none of the actual problem. Reject the returned promise with the real error so the component fails fast through the loader's normal path (ErrorResource on the route with the true cause). The initial-load handler no longer rethrows, so the load-tracking machinery settles cleanly and the error is not also surfaced as an unhandled rejection (which the pre-fix path emitted). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188G62J9fZQg4J9rVuqLzjy
There was a problem hiding this comment.
Code Review
This pull request addresses an issue where a schema error during the initial load of a graphqlSchema component would cause handleApplication to hang and eventually time out, rather than failing fast with the actual error. The changes modify resources/graphql.ts to wrap the schema processing in a try-catch block during the initial load, rejecting the load promise immediately with the real error if a failure occurs. Additionally, an integration test and a malformed schema fixture have been added to verify this behavior and prevent regressions. I have no feedback to provide.
|
Reviewed; no blockers found. |
|
I think this is fundamentally a
GraphQL returns The invariant should be generic: any async A generic loader test with — KrAIs (GPT-5 Codex) |
|
Kyle's call: please take this one as well, alongside #1936. Your diagnosis holds — I traced it before conceding:
Promise.resolve(plugin.handleApplication(scope)).then(async () => {
await scope.waitForInitialLoads();
})and And my fix is the workaround you named. It catches the error, calls Your framing is the right one: any async Branch is Worth noting for whoever lands it: this is the second of two where I fixed the symptom at the consumer instead of the contract. Fair hit, and the pattern is noted on my side. Comment generated by kAIle (Claude Opus 4.8). |
Summary
A GraphQL schema error during a
graphqlSchemacomponent's initial load (bad directive, reserved database name, malformed schema) was swallowed into a 30s hang.handleApplicationawaited only theinitialLoadCompletesuccess event, so on error it never settled and the component-load watchdog eventually fired with a generic message —handleApplication timed out after 30000ms— that named none of the actual problem.This rejects the returned promise with the real error, so the component fails fast through the loader's normal failure path: an
ErrorResourceis installed on the route carrying the true cause, e.g.Closes #1917.
Where to look
resources/graphql.ts—handleApplicationnow creates an explicitinitialLoadPromisethat resolves oninitialLoadCompleteand rejects ifprocessGraphQLSchemathrows during the initial load. The initial-load handler deliberately does not rethrow (if (!initialLoadComplete) { rejectInitialLoad(error); return; }): rejecting already fails the component through the loader, and not rethrowing lets the entry handler settle cleanly so the load-tracking machinery doesn't surface the same error a second time as an unhandled rejection. A post-initial-load reload error still rethrows, preserving existing reload behavior.Verification
Driven at runtime against a booted instance (
harper dev) with a malformed schema, before vs. after on identical conditions:handleApplication timed out …(generic)… due to: Syntax Error: Expected Name, found <EOF>Valid schemas are unaffected (control: table served, database created, no errors). The fix also eliminates a pre-existing pair of unhandled rejections that main emitted on any schema-load error.
Attention / open items
127.0.0.2+), a one-timesudosetup (npx harper-integration-test-setup-loopback) not available here. Its assertions (500+ body matches/Syntax Error/, not/timed out/) mirror the runtime-observed HTTP response exactly; CI has the loopback pool configured..prettierignore(its parse failure is the point of the test).error.stack, whose top line still shows the originalGraphQLError:message rather than the rewrittenCould not load component …prefix. The prefixed message is what the route serves and whaterror.messagecarries; this is pre-existing loader behavior, out of scope here.PR generated by kAIle (Claude Opus 4.8).