gastownhall/beads · error
failed to parse GraphQL response: %w
Error message
failed to parse GraphQL response: %w
What it means
graphqlRequest received an HTTP response from /api/graphql but the body could not be unmarshaled into the envelope {data, errors}. Unlike the REST path, GraphQL typically always returns valid JSON, so this usually indicates the response is not actually from the GraphQL endpoint — an HTML page, a redirect, or an empty/garbled body.
Source
Thrown at internal/gitlab/client.go:521
if len(variables) > 0 {
body["variables"] = variables
}
// GraphQL endpoint is at /api/graphql (not under /api/v4/)
urlStr := c.BaseURL + "/api/graphql"
respBody, _, err := c.doRequest(ctx, http.MethodPost, urlStr, body)
if err != nil {
return nil, fmt.Errorf("GraphQL request failed: %w", err)
}
var result struct {
Data json.RawMessage `json:"data"`
Errors []struct {
Message string `json:"message"`
} `json:"errors"`
}
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to parse GraphQL response: %w", err)
}
if len(result.Errors) > 0 {
return nil, fmt.Errorf("GraphQL error: %s", result.Errors[0].Message)
}
return result.Data, nil
}
// WorkItem represents a GitLab work item from the GraphQL API.
type WorkItem struct {
ID string `json:"id"` // Global ID (gid://gitlab/WorkItem/123)
IID string `json:"iid"` // Project-scoped ID
Title string `json:"title"`
Type string `json:"type"` // Work item type name
}
// defaultTaskTypeID is the fallback GID for older GitLab instances where the
// workItemTypes GraphQL query is unavailable.
const defaultTaskTypeID = "gid://gitlab/WorkItems::Type/5"View on GitHub (pinned to 71377f2769)
Solutions
- Log respBody on parse failure to identify what was actually returned.
- Confirm the URL is <BaseURL>/api/graphql and not being redirected (check for 3xx handling).
- Bypass proxies/SSO for the API host or configure token-based auth headers doRequest sends.
- Retry the request if the network is known-flaky; a truncated body will also fail to parse.
Example fix
null
Defensive patterns
Strategy: retry
Validate before calling
// verify endpoint returns JSON before making real calls
req, _ := http.NewRequest(http.MethodPost, client.BaseURL+"/api/graphql", nil)
resp, err := http.DefaultClient.Do(req)
if err == nil && !strings.Contains(resp.Header.Get("Content-Type"), "json") {
return fmt.Errorf("/api/graphql returned %s, not JSON", resp.Header.Get("Content-Type"))
} Try / catch
data, err := client.GetWorkItemGID(ctx, projectPath)
if err != nil {
if strings.Contains(err.Error(), "failed to parse GraphQL response") {
// non-JSON body: retry once after delay, then surface config issue
}
return err
} Prevention
- Bypass SSO/proxy for API hosts or use token auth that skips interactive redirects.
- Monitor for load-balancer error pages masquerading as API responses.
- Retry idempotent reads (GetWorkItemGID) on parse failures caused by truncation.
When it happens
Trigger: Calls to getTaskWorkItemTypeID, CreateTaskWorkItem, or GetWorkItemGID where the server returns 200 with an HTML login page, a proxy error page, an empty body, or a non-JSON content type on /api/graphql.
Common situations: SSO/redirect interposing on the API; a load balancer returning a 502 HTML page with status 200 suppressed; hitting the wrong port where another service responds; truncated responses from a flaky network via doRequest not validating status.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to parse work item response: %w
- unable to parse plugin file: %w
- parse gh output: %w
- parse %s: %w
- json: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4b1b65357cc75580.
Report an issue: GitHub.