{"record":{"id":"1d2cc09207e5875d","repo":"chenhg5/cc-connect","slug":"googlechat-upload-decode-response-w","errorCode":null,"errorMessage":"googlechat: upload: decode response: %w","messagePattern":"googlechat: upload: decode response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/googlechat/googlechat.go","lineNumber":501,"sourceCode":"\n\tresp, err := p.doRequest(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer func() {\n\t\tif err := resp.Body.Close(); err != nil {\n\t\t\tslog.Warn(\"googlechat: close upload response body\", \"error\", err)\n\t\t}\n\t}()\n\n\tvar result struct {\n\t\tAttachmentDataRef struct {\n\t\t\tResourceName string `json:\"resourceName\"`\n\t\t} `json:\"attachmentDataRef\"`\n\t}\n\tdefer func() { _, _ = io.Copy(io.Discard, resp.Body) }()\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"googlechat: upload: decode response: %w\", err)\n\t}\n\tif result.AttachmentDataRef.ResourceName == \"\" {\n\t\treturn \"\", fmt.Errorf(\"googlechat: upload: empty resourceName in response\")\n\t}\n\treturn result.AttachmentDataRef.ResourceName, nil\n}\n\n// buildAttachmentRequest builds the Chat REST API URL and JSON body to post a\n// message that references an already-uploaded attachment (by resource name).\n// Threading behaviour mirrors buildSendRequest.\nfunc buildAttachmentRequest(rc replyContext, resourceName string) (string, []byte, error) {\n\tbody := map[string]any{\n\t\t\"attachment\": []map[string]any{\n\t\t\t{\"attachmentDataRef\": map[string]any{\"resourceName\": resourceName}},\n\t\t},\n\t}\n\tapplyThread(body, rc)\n\tb, err := json.Marshal(body)","sourceCodeStart":483,"sourceCodeEnd":519,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/googlechat/googlechat.go#L483-L519","documentation":"Returned by uploadAttachment when the JSON response from the Chat media upload endpoint cannot be decoded into the expected {attachmentDataRef:{resourceName}} structure. Typically the server returned an error payload (HTML error page, OAuth error JSON, or empty body) rather than the expected shape. It surfaces via postAttachment from SendImage/SendFile.","triggerScenarios":"Calling SendImage()/SendFile(); the upload POST returns a response whose body is not valid JSON or does not match the result struct — e.g. an auth error body, truncated response, or proxy-injected content.","commonSituations":"Expired/missing OAuth credentials returning an error JSON with unexpected fields; a proxy returning an HTML error page; API version changes altering the response schema; non-2xx responses not being checked before decode.","solutions":["Verify Google Chat API credentials and scopes (chat.messages / media upload scope) are valid and unexpired.","Log the raw response body and HTTP status before decoding to see what the server actually returned.","Check googleapis.com reachability — a captive portal/proxy may inject non-JSON content.","Pin/verify the Chat REST API version used by the library; response schema changes break decoding."],"exampleFix":"// before\nif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n    return \"\", fmt.Errorf(\"googlechat: upload: decode response: %w\", err)\n}\n// after\nraw, _ := io.ReadAll(resp.Body)\nif resp.StatusCode != http.StatusOK {\n    return \"\", fmt.Errorf(\"googlechat: upload: status %d: %s\", resp.StatusCode, raw)\n}\nif err := json.Unmarshal(raw, &result); err != nil {\n    return \"\", fmt.Errorf(\"googlechat: upload: decode response: %w\", err)\n}","handlingStrategy":"retry","validationCode":"// pre-flight: verify credentials/scopes before uploading\nif !checkOAuthToken(ctx) { // refresh token if expired\n    return fmt.Errorf(\"google chat credentials invalid or expired\")\n}","typeGuard":"func hasAttachmentDataRef(raw []byte) bool {\n    var probe struct {\n        AttachmentDataRef struct {\n            ResourceName string `json:\"resourceName\"`\n        } `json:\"attachmentDataRef\"`\n    }\n    return json.Unmarshal(raw, &probe) == nil\n}","tryCatchPattern":"name, err := p.SendFile(ctx, target, path)\nif err != nil {\n    if strings.Contains(err.Error(), \"decode response\") && isTransient(err) {\n        // retry once after refreshing credentials\n        time.Sleep(time.Second)\n        return p.SendFile(ctx, target, path)\n    }\n    return err\n}","preventionTips":["Keep OAuth tokens refreshed and scopes correct for chat media upload.","Ensure no intercepting proxies inject HTML into googleapis.com responses.","Log HTTP status and raw body before JSON decoding in custom builds.","Pin the library to a version matching the current Chat REST API response schema."],"tags":["googlechat","upload","json","api-response"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}