{"record":{"id":"4b1b65357cc75580","repo":"gastownhall/beads","slug":"failed-to-parse-graphql-response-w","errorCode":null,"errorMessage":"failed to parse GraphQL response: %w","messagePattern":"failed to parse GraphQL response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/gitlab/client.go","lineNumber":521,"sourceCode":"\tif len(variables) > 0 {\n\t\tbody[\"variables\"] = variables\n\t}\n\n\t// GraphQL endpoint is at /api/graphql (not under /api/v4/)\n\turlStr := c.BaseURL + \"/api/graphql\"\n\trespBody, _, err := c.doRequest(ctx, http.MethodPost, urlStr, body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"GraphQL request failed: %w\", err)\n\t}\n\n\tvar result struct {\n\t\tData   json.RawMessage `json:\"data\"`\n\t\tErrors []struct {\n\t\t\tMessage string `json:\"message\"`\n\t\t} `json:\"errors\"`\n\t}\n\tif err := json.Unmarshal(respBody, &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse GraphQL response: %w\", err)\n\t}\n\tif len(result.Errors) > 0 {\n\t\treturn nil, fmt.Errorf(\"GraphQL error: %s\", result.Errors[0].Message)\n\t}\n\treturn result.Data, nil\n}\n\n// WorkItem represents a GitLab work item from the GraphQL API.\ntype WorkItem struct {\n\tID    string `json:\"id\"`  // Global ID (gid://gitlab/WorkItem/123)\n\tIID   string `json:\"iid\"` // Project-scoped ID\n\tTitle string `json:\"title\"`\n\tType  string `json:\"type\"` // Work item type name\n}\n\n// defaultTaskTypeID is the fallback GID for older GitLab instances where the\n// workItemTypes GraphQL query is unavailable.\nconst defaultTaskTypeID = \"gid://gitlab/WorkItems::Type/5\"","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/gitlab/client.go#L503-L539","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"null","handlingStrategy":"retry","validationCode":"// verify endpoint returns JSON before making real calls\nreq, _ := http.NewRequest(http.MethodPost, client.BaseURL+\"/api/graphql\", nil)\nresp, err := http.DefaultClient.Do(req)\nif err == nil && !strings.Contains(resp.Header.Get(\"Content-Type\"), \"json\") {\n    return fmt.Errorf(\"/api/graphql returned %s, not JSON\", resp.Header.Get(\"Content-Type\"))\n}","typeGuard":null,"tryCatchPattern":"data, err := client.GetWorkItemGID(ctx, projectPath)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to parse GraphQL response\") {\n        // non-JSON body: retry once after delay, then surface config issue\n    }\n    return err\n}","preventionTips":["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."],"tags":["gitlab","graphql","json","parse-error"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}