{"record":{"id":"feb9f8f2750b9531","repo":"ZhuLinsen/daily_stock_analysis","slug":"http-resp2-status-code","errorCode":null,"errorMessage":"HTTP {resp2.status_code}","messagePattern":"HTTP \\{resp2\\.status_code\\}","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/notification_sender/slack_sender.py","lineNumber":218,"sourceCode":"                )\n                result1 = resp1.json()\n                if not result1.get(\"ok\"):\n                    logger.error(\"Slack 获取上传 URL 失败: %s\", result1.get('error', 'unknown'))\n                    raise RuntimeError(result1.get('error', 'unknown'))\n\n                upload_url = result1['upload_url']\n                file_id = result1['file_id']\n\n                # Step 2: 上传文件内容（raw body，不能用 multipart）\n                resp2 = requests.post(\n                    upload_url,\n                    data=image_bytes,\n                    headers={'Content-Type': 'application/octet-stream'},\n                    timeout=30,\n                )\n                if resp2.status_code != 200:\n                    logger.error(\"Slack 文件上传失败: HTTP %s\", resp2.status_code)\n                    raise RuntimeError(f\"HTTP {resp2.status_code}\")\n\n                # Step 3: 完成上传并分享到频道\n                resp3 = requests.post(\n                    'https://slack.com/api/files.completeUploadExternal',\n                    headers={**headers, 'Content-Type': 'application/json'},\n                    json={\n                        'files': [{'id': file_id, 'title': '股票分析报告'}],\n                        'channel_id': self._slack_channel_id,\n                    },\n                    timeout=30,\n                )\n                result3 = resp3.json()\n                if result3.get(\"ok\"):\n                    logger.info(\"Slack Bot 图片发送成功\")\n                    return True\n                logger.error(\"Slack 完成上传失败: %s\", result3.get('error', 'unknown'))\n            except Exception as e:\n                logger.error(\"Slack Bot 图片发送异常: %s\", e)","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/notification_sender/slack_sender.py#L200-L236","documentation":"RuntimeError raised in the Slack sender when step 2 of the file upload — POSTing the raw PNG bytes to the upload_url returned by files.getUploadURLExternal — responds with a non-200 HTTP status. Unlike step 1, this endpoint is not the JSON API, so only the HTTP status is checked and surfaced as 'HTTP <status>'.","triggerScenarios":"The POST of image_bytes (Content-Type: application/octet-stream) to upload_url returns 4xx/5xx: 403 when the pre-signed upload URL expired or was already used, 400 when the body length does not match the 'length' declared in step 1, 5xx on Slack infra issues, or timeouts/empty bytes producing edge failures.","commonSituations":"Declaring a length different from the actual image byte count; retrying an already-consumed upload URL; long delays between step 1 and step 2 letting the URL expire; corporate proxies mangling binary request bodies; transient Slack upload-host errors.","solutions":["Retry the whole 3-step upload once — expired/one-shot upload URLs and transient 5xx are common and a fresh sequence fixes them","Ensure the 'length' value passed to files.getUploadURLExternal equals len(image_bytes) exactly","Check for proxies/interceptors that alter binary bodies or strip headers, and bypass them for slack.com","If persistent, capture resp2 body/text for the specific status code and consult Slack upload docs for that failure"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"def upload_lengths_consistent(length_declared: int, payload: bytes) -> bool:\n    return length_declared == len(payload)","typeGuard":null,"tryCatchPattern":"for attempt in range(2):\n    try:\n        send_slack_image(channel, image_bytes)\n        break\n    except RuntimeError as exc:\n        if str(exc).startswith(\"HTTP \") and attempt == 0:\n            time.sleep(1)  # fresh upload URL next round\n            continue\n        raise","preventionTips":["Always pass length=len(image_bytes) exactly to getUploadURLExternal","Upload immediately after obtaining the URL — they expire and are single-use","Do steps 1-3 in one function so retries always restart the full sequence"],"tags":["slack","notification","upload","http"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}