{"record":{"id":"ce9c1fa3b47f5f30","repo":"glanceapp/glance","slug":"unmarshalling-stream-metadata-w","errorCode":null,"errorMessage":"unmarshalling stream metadata: %w","messagePattern":"unmarshalling stream metadata: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-twitch-channels.go","lineNumber":162,"sourceCode":"\t\treturn result, err\n\t}\n\n\tif len(response) != 2 {\n\t\treturn result, fmt.Errorf(\"expected 2 operation responses, got %d\", len(response))\n\t}\n\n\tvar channelShell twitchChannelShellOperationResponse\n\tvar streamMetadata twitchStreamMetadataOperationResponse\n\n\tfor i := range response {\n\t\tswitch response[i].Extensions.OperationName {\n\t\tcase \"ChannelShell\":\n\t\t\tif err = json.Unmarshal(response[i].Data, &channelShell); err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"unmarshalling channel shell: %w\", err)\n\t\t\t}\n\t\tcase \"StreamMetadata\":\n\t\t\tif err = json.Unmarshal(response[i].Data, &streamMetadata); err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"unmarshalling stream metadata: %w\", err)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn result, fmt.Errorf(\"unknown operation name: %s\", response[i].Extensions.OperationName)\n\t\t}\n\t}\n\n\tif channelShell.UserOrError.Type != \"User\" {\n\t\tresult.Name = result.Login\n\t\treturn result, nil\n\t}\n\n\tresult.Exists = true\n\tresult.Name = channelShell.UserOrError.DisplayName\n\tresult.AvatarUrl = channelShell.UserOrError.ProfileImageUrl\n\n\tif channelShell.UserOrError.Stream != nil {\n\t\tresult.IsLive = true\n\t\tresult.ViewersCount = channelShell.UserOrError.Stream.ViewersCount","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-twitch-channels.go#L144-L180","documentation":"Emitted while parsing the 'StreamMetadata' half of the same Twitch GQL batched request. json.Unmarshal of the StreamMetadata payload into twitchStreamMetadataOperationResponse failed, meaning the returned JSON does not fit the expected struct. Like the channel shell error, this indicates Twitch altered the undocumented GQL schema.","triggerScenarios":"A StreamMetadata operation response whose Data changed type or shape (e.g. stream field null for offline channels, viewer count becoming a string, removed nodes), causing Unmarshal to return a *json.UnmarshalTypeError.","commonSituations":"All Twitch channels in the widget show as offline/fail after a Twitch-side change; offline channels where stream metadata is null; running an old Glance version against a changed endpoint.","solutions":["Log string(response[i].Data) at the failure point and compare with the struct fields to find the mismatch","Adjust twitchStreamMetadataOperationResponse (nullable pointers for optional fields) in internal/glance/widget-twitch-channels.go","Update Glance to the latest version where the fix has likely already landed"],"exampleFix":"// before\nif err = json.Unmarshal(response[i].Data, &streamMetadata); err != nil {\n    return result, fmt.Errorf(\"unmarshalling stream metadata: %w\", err)\n}\n\n// after (tolerate null stream for offline channels)\ntype twitchStreamMetadataOperationResponse struct {\n    User *struct {\n        Stream *struct {\n            ViewersCount int `json:\"viewersCount\"`\n        } `json:\"stream\"`\n    } `json:\"user\"`\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":"func isTwitchUnmarshalErr(err error) bool {\n\tvar syntaxErr *json.SyntaxError\n\tvar typeErr *json.UnmarshalTypeError\n\treturn errors.As(err, &syntaxErr) || errors.As(err, &typeErr)\n}","tryCatchPattern":"if err != nil {\n    if isTwitchUnmarshalErr(err) {\n        slog.Warn(\"twitch stream metadata shape changed\", \"err\", err)\n        // proceed with offline/fallback channel rendering\n    }\n}","preventionTips":["Make optional GQL fields pointer types so null payloads unmarshal cleanly","Log raw payloads at debug level to shorten diagnosis of schema drift","Track upstream Glance releases that patch Twitch breakage"],"tags":["twitch","json","api-change","widget"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}