{"record":{"id":"31a4693c7d78ba53","repo":"glanceapp/glance","slug":"no-categories-could-be-retrieved","errorCode":null,"errorMessage":"no categories could be retrieved","messagePattern":"no categories could be retrieved","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-twitch-top-games.go","lineNumber":91,"sourceCode":"\t\t} `json:\"directoriesWithTags\"`\n\t} `json:\"data\"`\n}\n\nconst twitchDirectoriesOperationRequestBody = `[\n{\"operationName\": \"BrowsePage_AllDirectories\",\"variables\": {\"limit\": %d,\"options\": {\"sort\": \"VIEWER_COUNT\",\"tags\": []}},\"extensions\": {\"persistedQuery\": {\"version\": 1,\"sha256Hash\": \"2f67f71ba89f3c0ed26a141ec00da1defecb2303595f5cda4298169549783d9e\"}}}\n]`\n\nfunc fetchTopGamesFromTwitch(exclude []string, limit int) ([]twitchCategory, error) {\n\treader := strings.NewReader(fmt.Sprintf(twitchDirectoriesOperationRequestBody, len(exclude)+limit))\n\trequest, _ := http.NewRequest(\"POST\", twitchGqlEndpoint, reader)\n\trequest.Header.Add(\"Client-ID\", twitchGqlClientId)\n\tresponse, err := decodeJsonFromRequest[[]twitchDirectoriesOperationResponse](defaultHTTPClient, request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(response) == 0 {\n\t\treturn nil, errors.New(\"no categories could be retrieved\")\n\t}\n\n\tedges := (response)[0].Data.DirectoriesWithTags.Edges\n\tcategories := make([]twitchCategory, 0, len(edges))\n\n\tfor i := range edges {\n\t\tif slices.Contains(exclude, edges[i].Node.Slug) {\n\t\t\tcontinue\n\t\t}\n\n\t\tcategory := &edges[i].Node\n\t\tcategory.AvatarUrl = strings.Replace(category.AvatarUrl, \"285x380\", \"144x192\", 1)\n\n\t\tif len(category.Tags) > 2 {\n\t\t\tcategory.Tags = category.Tags[:2]\n\t\t}\n\n\t\tgameReleasedDate, err := time.Parse(\"2006-01-02T15:04:05Z\", category.GameReleaseDate)","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-twitch-top-games.go#L73-L109","documentation":"Thrown by fetchTopGamesFromTwitch when the Twitch GQL endpoint (maestro.gql.twitch.com) returns HTTP 200 with a JSON body that decodes to an empty array. Glance queries Twitch's internal persisted-query GraphQL API with a hardcoded sha256Hash; if Twitch changes/retires that persisted query or the response shape, the outer array decodes but is empty, so no categories can be extracted. It is a data-shape/empty-response error, not a transport error (transport errors return earlier from decodeJsonFromRequest).","triggerScenarios":"POST to twitchGqlEndpoint with the BrowsePage_AllDirectories persisted query returns a JSON array of length 0 (e.g. Twitch returns [] or an errors-only payload that unmarshals to zero elements); happens when the hardcoded sha256Hash 2f67f71b... is revoked, when Twitch's GQL gateway behaves differently, or during transient Twitch-side anomalies that still yield 200 with an empty batch response.","commonSituations":"A Glance version upgrade is needed after Twitch rotated its persisted query hash; running an old Glance image; intermittent Twitch GQL behavior behind rate limiting or regional gateways; corporate proxies returning 200 with rewritten bodies.","solutions":["Check if a newer Glance release fixes the Twitch persisted query hash and upgrade (this error usually means the internal Twitch API contract drifted).","Run `glance diagnose` and manually test connectivity to maestro.gql.twitch.com from the host (proxy/firewall can rewrite responses).","Retry after a few minutes to rule out a transient Twitch-side empty response; the widget caches 10 minutes.","If persistent, temporarily remove or comment out the twitch-top-games widget so the rest of the dashboard renders.","If developing locally, POST the twitchDirectoriesOperationRequestBody to the endpoint with curl and inspect the body to confirm whether data.directoriesWithTags.edges is present."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// In a custom build calling fetchTopGamesFromTwitch indirectly via the widget:\n// no direct API; treat any update error shown on the widget as transient first.\nif err != nil {\n    if strings.Contains(err.Error(), \"no categories could be retrieved\") {\n        // transient or upstream contract drift: wait for next cached update,\n        // check for a newer Glance release\n    }\n}","preventionTips":["Pin a recent Glance version so the Twitch persisted-query hash stays current.","Monitor the Glance repo issues for 'twitch' breakage reports after Twitch API changes.","Keep the widget's exclude/limit within sane bounds so the requested batch size stays valid."],"tags":["twitch","graphql","api-contract","empty-response","widget"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}