{"record":{"id":"ec614ded5c50482e","repo":"binary-husky/gpt_academic","slug":"slack","errorCode":null,"errorMessage":"获取Slack消息失败。","messagePattern":"获取Slack消息失败。","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"request_llms/bridge_stackclaude.py","lineNumber":67,"sourceCode":"                raise Exception(\"Channel not found.\")\n\n            resp = await self.chat_postMessage(channel=self.CHANNEL_ID, text=text)\n            self.LAST_TS = resp[\"ts\"]\n\n        async def get_slack_messages(self):\n            try:\n                # TODO：暂时不支持历史消息，因为在同一个频道里存在多人使用时历史消息渗透问题\n                resp = await self.conversations_history(\n                    channel=self.CHANNEL_ID, oldest=self.LAST_TS, limit=1\n                )\n                msg = [\n                    msg\n                    for msg in resp[\"messages\"]\n                    if msg.get(\"user\") == get_conf(\"SLACK_CLAUDE_BOT_ID\")\n                ]\n                return msg\n            except (SlackApiError, KeyError) as e:\n                raise RuntimeError(f\"获取Slack消息失败。\")\n\n        async def get_reply(self):\n            while True:\n                slack_msgs = await self.get_slack_messages()\n                if len(slack_msgs) == 0:\n                    await asyncio.sleep(0.5)\n                    continue\n\n                msg = slack_msgs[-1]\n                if msg[\"text\"].endswith(\"Typing…_\"):\n                    yield False, msg[\"text\"]\n                else:\n                    yield True, msg[\"text\"]\n                    break\n\nexcept:\n    pass\n","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/binary-husky/gpt_academic/blob/d6bde0fa54373309bd05823a49bda8da019d2c77/request_llms/bridge_stackclaude.py#L49-L85","documentation":"A Slack API failure while fetching the bot's replies: get_slack_messages() calls conversations_history and wraps SlackApiError / KeyError in RuntimeError('获取Slack消息失败。'). SlackApiError means the Slack HTTP API returned an error payload (bad token, missing scope, invalid channel); KeyError means the response JSON lacked the expected 'messages' field.","triggerScenarios":"conversations_history(channel=CHANNEL_ID, oldest=LAST_TS, limit=1) failing inside the get_reply() polling loop of request_llms/bridge_stackclaude.py:67 — e.g. expired/revoked SLACK_CLAUDE_USER_TOKEN, token without channels:history scope, CHANNEL_ID pointing to an inaccessible channel, or Slack returning an error shape without resp['messages'].","commonSituations":"User token rotated or revoked after setup; bot removed from workspace; missing history scope on the legacy token; Slack rate limit (429) during the 0.5s polling loop; CHANNEL_ID stale after workspace migration.","solutions":["Verify SLACK_CLAUDE_USER_TOKEN is valid: curl -H 'Authorization: Bearer <token>' https://slack.com/api/auth.test","Add the required read scope (im:history / channels:history) to the token and reinstall the app","Check you are not rate-limited: the get_reply loop polls every 0.5s, back off if Slack returns 429","Catch SlackApiError and log e.response['error'] to see the exact Slack code (invalid_auth, channel_not_found, missing_scope, ratelimited)"],"exampleFix":"# before\nexcept (SlackApiError, KeyError) as e:\n    raise RuntimeError(f\"获取Slack消息失败。\")\n\n# after\nexcept SlackApiError as e:\n    raise RuntimeError(f\"获取Slack消息失败: {e.response['error']}\") from e\nexcept KeyError as e:\n    raise RuntimeError(f\"获取Slack消息失败: unexpected payload, missing {e}\") from e","handlingStrategy":"try-catch","validationCode":"from slack.errors import SlackApiError\ntest = await client.conversations_history(channel=client.CHANNEL_ID, limit=1)\nassert 'messages' in test, 'unexpected Slack payload shape'","typeGuard":null,"tryCatchPattern":"try:\n    msgs = await client.get_slack_messages()\nexcept RuntimeError:\n    logger.exception('conversations_history failed; token/scopes/rate-limit?')\n    await asyncio.sleep(2)  # back off, loop continues\nexcept Exception:\n    raise","preventionTips":["Log e.response['error'] from SlackApiError instead of swallowing it","Back off (exponential) when Slack returns 429 in the polling loop","Run auth.test on the token at startup to catch revocation early"],"tags":["slack","network","api-error","claude-bridge"],"backgroundTag":null,"analyzedSha":"d6bde0fa54373309bd05823a49bda8da019d2c77","analyzedAt":"2026-08-14T22:48:35.038Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}