{"record":{"id":"6659fab2c914490f","repo":"chenhg5/cc-connect","slug":"received-html-response-likely-missing-auth-firs","errorCode":null,"errorMessage":"received HTML response (likely missing auth); first 100 bytes: %s","messagePattern":"received HTML response \\(likely missing auth\\); first 100 bytes: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/slack/slack.go","lineNumber":587,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s\", core.RedactToken(err.Error(), p.botToken))\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check if we got an unexpected status code (e.g., redirect to login page)\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))\n\t\treturn nil, fmt.Errorf(\"download failed with status %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read response body: %w\", err)\n\t}\n\n\t// Basic sanity check: detect if we received HTML instead of binary data\n\tif len(data) > 0 && (bytes.HasPrefix(data, []byte(\"<!DOCTYPE\")) || bytes.HasPrefix(data, []byte(\"<html\"))) {\n\t\treturn nil, fmt.Errorf(\"received HTML response (likely missing auth); first 100 bytes: %s\", string(data[:min(100, len(data))]))\n\t}\n\n\treturn data, nil\n}\n\nfunc (p *Platform) ReconstructReplyCtx(sessionKey string) (any, error) {\n\t// slack:{channel}:{user}  |  slack:{channel}:t:{threadTS}  |  slack:{channel}\n\tparts := strings.SplitN(sessionKey, \":\", 3)\n\tif len(parts) < 2 || parts[0] != \"slack\" {\n\t\treturn nil, fmt.Errorf(\"slack: invalid session key %q\", sessionKey)\n\t}\n\trc := replyContext{channel: parts[1]}\n\t// Thread-scoped keys carry the thread root ts as a \"t:<ts>\" suffix; preserve\n\t// it so proactive replies (cron, send-to-session, restart/model/delete\n\t// notifications) post into the original thread instead of the channel root.\n\tif len(parts) == 3 && strings.HasPrefix(parts[2], \"t:\") {\n\t\trc.timestamp = strings.TrimPrefix(parts[2], \"t:\")\n\t}","sourceCodeStart":569,"sourceCodeEnd":605,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/slack/slack.go#L569-L605","documentation":"After reading the body, downloadSlackFile sanity-checks whether Slack returned an HTML page (starting with <!DOCTYPE or <html) instead of the expected binary file content. This almost always means the request was not authenticated and Slack served a login/error page. The first 100 bytes are included to aid diagnosis.","triggerScenarios":"The file URL requires auth but the request was sent without (or with an invalid/expired) Authorization: Bearer token; a redirect landed on a Slack sign-in HTML page; the URL is a browser page URL rather than the url_private_download endpoint.","commonSituations":"Using public_permalink instead of url_private_download; token lacking files:read scope; user token vs bot token mismatch; expired token after workspace app re-install; corporate proxy returning an HTML interstitial.","solutions":["Ensure the request carries the bot token: pass slack.MsgOption or set Authorization: Bearer <token> on the http request using the file's url_private_download field","Verify the bot token has the files:read scope (check via auth.test / app config)","Use file.URLPrivateDownload, not file.Permalink or URLPrivate, for programmatic download","Inspect the logged first-100-bytes snippet to confirm it is a login/permission page"],"exampleFix":"// before\nreq, _ := http.NewRequest(\"GET\", fileURL, nil)\nresp, err := http.DefaultClient.Do(req)\n// after\nreq, _ := http.NewRequest(\"GET\", fileURL, nil) // fileURL = file.URLPrivateDownload\nreq.Header.Set(\"Authorization\", \"Bearer \"+p.botToken)\nresp, err := http.DefaultClient.Do(req)","handlingStrategy":"validation","validationCode":"func fileURLIsDownloadable(f *slack.File) bool {\n    return f.URLPrivateDownload != \"\" // use this URL + Bearer token\n}","typeGuard":null,"tryCatchPattern":"data, err := downloadSlackFile(f.URLPrivateDownload, token)\nif err != nil {\n    if strings.Contains(err.Error(), \"received HTML response\") {\n        return fmt.Errorf(\"slack auth/URL problem: %w\", err) // fix token/URL, don't retry\n    }\n    return err\n}","preventionTips":["Always download from url_private_download with an Authorization: Bearer bot token","Verify files:read scope when installing the Slack app","Never use public_permalink for programmatic downloads","Never hit the URL without the auth header"],"tags":["slack","authentication","html-response","file-download"],"backgroundTag":"unexpected-api-response-shape","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"}