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
LabelService.listLabels() — remove the try/catch at lines 41/62-69. Let GraphqlService.query()'s exceptions propagate unmodified. Update JSDoc @throws contract.
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
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
The Problem
The CI
data-sync-pipelineworkflow 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()atai/mcp/server/github-workflow/services/LabelService.mjs:36-70returns two different shapes depending on success:{count, labels}) — 2 fields{error, message, code}) — 3 different fields;messageis justerror.messagewith no status code, stack, or structured contextThe CLI in
buildScripts/docs/index/labels.mjs:58-60checks!response || !response.labelsand 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-222already 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
LabelServiceonly — 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
LabelService.listLabels()— remove thetry/catchat lines 41/62-69. LetGraphqlService.query()'s exceptions propagate unmodified. Update JSDoc@throwscontract.buildScripts/docs/index/labels.mjs— drop the!response || !response.labelscheck at lines 58-60. The real GraphQL error (with status code + message) now bubbles up through thetry/catchat 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
LabelService. The correct layer for retry isGraphqlService(cross-cutting primitive). Out of scope here — only introduce retry if observed failures justify it after diagnostics are restored.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}@throwsannotationbuildScripts/docs/index/labels.mjsno longer shape-checks the responseRelated
IssueService,DiscussionService,PullRequestServiceall use the same wrap-and-return pattern internally, harmless through MCP boundaryOrigin Session ID
07f601dc-353a-44d2-a373-18da2a0d305a