LearnNewsExamplesServices
Frontmatter
id10112
titlePropagate GraphQL errors through LabelService instead of returning wrapper objects
stateClosed
labels
bugaiarchitecture
assigneestobiu
createdAtApr 20, 2026, 12:59 AM
updatedAtApr 20, 2026, 1:13 AM
githubUrlhttps://github.com/neomjs/neo/issues/10112
authortobiu
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtApr 20, 2026, 1:13 AM

Propagate GraphQL errors through LabelService instead of returning wrapper objects

Closedbugaiarchitecture
tobiu
tobiu commented on Apr 20, 2026, 12:59 AM

The Problem

The CI data-sync-pipeline workflow intermittently fails with a useless error message:

Error generating label index: Error: Invalid response from GH_LabelService
    at createLabelIndex (.../buildScripts/docs/index/labels.mjs:59:19)

The error the script reports is a downstream wrapper — the actual GraphQL failure (rate-limit 429? 5xx? network?) is structurally hidden. We cannot tell from CI logs what actually went wrong.

The Architectural Reality

LabelService.listLabels() at ai/mcp/server/github-workflow/services/LabelService.mjs:36-70 returns two different shapes depending on success:

  • Happy path ({count, labels}) — 2 fields
  • Error path ({error, message, code}) — 3 different fields; message is just error.message with no status code, stack, or structured context

The CLI in buildScripts/docs/index/labels.mjs:58-60 checks !response || !response.labels and throws its own generic 'Invalid response from GH_LabelService', replacing the already-weak wrapper with an even weaker string. Original diagnostics are lost twice over.

The Anti-Pattern

This swallow-and-wrap exists at 11 call sites across 4 services (IssueService × 6, DiscussionService × 2, PullRequestService × 2, LabelService × 1). The wrapping is redundant with the MCP tool boundary: Server.mjs:150-222 already catches thrown exceptions from service methods and converts them to structured MCP error payloads for the protocol. Services catching and re-wrapping is a double-handling that only hurts non-MCP callers (CLI, build scripts, future consumers).

Scope of this ticket is deliberately narrow to LabelService only — the one service currently exposed through a CI-visible build script. The other 10 sites share the same anti-pattern but their failures surface through the MCP tool layer (which already produces usable error payloads), so they aren't user-visible in the same way. A broader cross-service cleanup would be its own architectural ticket; keeping this one scoped to the observed symptom.

Fix

  1. LabelService.listLabels() — remove the try/catch at lines 41/62-69. Let GraphqlService.query()'s exceptions propagate unmodified. Update JSDoc @throws contract.
  2. buildScripts/docs/index/labels.mjs — drop the !response || !response.labels check at lines 58-60. The real GraphQL error (with status code + message) now bubbles up through the try/catch at line 78.

Diagnostic Payoff

The PR itself validates (or refutes) the prevailing hypothesis: devindex spider's 3x loop is consuming GraphQL rate-limit budget → labels call gets 429 → label index generation fails. Once the real error is visible in CI logs, we confirm or reject the hypothesis empirically. If 429 confirmed, the root-cause fix is tracked separately (reduce spider loop frequency).

Avoided Traps

  • Retry inside LabelService. The correct layer for retry is GraphqlService (cross-cutting primitive). Out of scope here — only introduce retry if observed failures justify it after diagnostics are restored.
  • Touching the 10 sibling swallow-sites in other services. Same anti-pattern but consumed via MCP tool boundary which already catches. Out of scope; factor into a cross-service cleanup ticket if/when pressure arises.
  • Changing the MCP tool-boundary behavior at Server.mjs:150-222. That layer's wrapping is correct (MCP protocol requires structured errors). Nothing to change there.

Acceptance Criteria

  • LabelService.listLabels() throws on GraphQL failure instead of returning {error, message, code}
  • JSDoc reflects throwing contract with @throws annotation
  • buildScripts/docs/index/labels.mjs no longer shape-checks the response
  • Next CI failure prints the original GraphQL error (status + message) in the pipeline log

Related

  • Likely companion: a ticket reducing DevIndex Spider loop from 3x→1x (rate-limit pressure root cause)
  • Unreached siblings (future cross-service cleanup if warranted): IssueService, DiscussionService, PullRequestService all use the same wrap-and-return pattern internally, harmless through MCP boundary

Origin Session ID

07f601dc-353a-44d2-a373-18da2a0d305a

tobiu added the bug label on Apr 20, 2026, 12:59 AM
tobiu added the ai label on Apr 20, 2026, 12:59 AM
tobiu added the architecture label on Apr 20, 2026, 12:59 AM
tobiu assigned to @tobiu on Apr 20, 2026, 12:59 AM
tobiu referenced in commit e23c596 - "fix(github-workflow): propagate GraphQL errors from LabelService (#10112)" on Apr 20, 2026, 1:01 AM
tobiu cross-referenced by PR #10114 on Apr 20, 2026, 1:02 AM
tobiu referenced in commit d8b99b2 - "fix(github-workflow): propagate GraphQL errors from LabelService (#10112) (#10114)" on Apr 20, 2026, 1:13 AM
tobiu closed this issue on Apr 20, 2026, 1:13 AM
tobiu cross-referenced by PR #10115 on Apr 20, 2026, 1:15 AM