ZhuLinsen/daily_stock_analysis · error · RuntimeError
HTTP {resp2.status_code}
Error message
HTTP {resp2.status_code} What it means
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>'.
Source
Thrown at src/notification_sender/slack_sender.py:218
)
result1 = resp1.json()
if not result1.get("ok"):
logger.error("Slack 获取上传 URL 失败: %s", result1.get('error', 'unknown'))
raise RuntimeError(result1.get('error', 'unknown'))
upload_url = result1['upload_url']
file_id = result1['file_id']
# Step 2: 上传文件内容(raw body,不能用 multipart)
resp2 = requests.post(
upload_url,
data=image_bytes,
headers={'Content-Type': 'application/octet-stream'},
timeout=30,
)
if resp2.status_code != 200:
logger.error("Slack 文件上传失败: HTTP %s", resp2.status_code)
raise RuntimeError(f"HTTP {resp2.status_code}")
# Step 3: 完成上传并分享到频道
resp3 = requests.post(
'https://slack.com/api/files.completeUploadExternal',
headers={**headers, 'Content-Type': 'application/json'},
json={
'files': [{'id': file_id, 'title': '股票分析报告'}],
'channel_id': self._slack_channel_id,
},
timeout=30,
)
result3 = resp3.json()
if result3.get("ok"):
logger.info("Slack Bot 图片发送成功")
return True
logger.error("Slack 完成上传失败: %s", result3.get('error', 'unknown'))
except Exception as e:
logger.error("Slack Bot 图片发送异常: %s", e)View on GitHub (pinned to 5159bd72e8)
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
Defensive patterns
Strategy: retry
Validate before calling
def upload_lengths_consistent(length_declared: int, payload: bytes) -> bool:
return length_declared == len(payload) Try / catch
for attempt in range(2):
try:
send_slack_image(channel, image_bytes)
break
except RuntimeError as exc:
if str(exc).startswith("HTTP ") and attempt == 0:
time.sleep(1) # fresh upload URL next round
continue
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/feb9f8f2750b9531.
Report an issue: GitHub.