Skip to content

Commit 76f817d

Browse files
author
Irving Popovetsky
committed
Fix SlackAPI session initialization race condition ( Sentry ID OPERATIONCODE-PYBOT-4T )
The SlackAPI was being initialized in plugin.load() before the aiohttp session existed, causing '_session' to be None. This led to production errors: "AttributeError: 'NoneType' object has no attribute 'request'". Solution: - Defer SlackAPI initialization to startup callback after session creation - Make _initialize_api() idempotent to preserve test mocks - Update test fixtures to manually trigger initialization when needed
1 parent 45009fa commit 76f817d

File tree

3 files changed

+25
-2
lines changed

3 files changed

+25
-2
lines changed

docker-build.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
#!/bin/sh -ex
22

3+
# Refresh AWS ECR login
4+
AWS_REGION=${AWS_REGION:-us-east-2}
5+
echo "Refreshing AWS ECR login for region ${AWS_REGION}..."
6+
aws ecr get-login-password --region ${AWS_REGION} | \
7+
docker login --username AWS --password-stdin 633607774026.dkr.ecr.${AWS_REGION}.amazonaws.com
8+
39
# Build and push ARM64 image using buildx with provenance disabled
410
docker buildx build \
511
--platform linux/arm64 \

pybot/_vendor/sirbot/plugins/slack/plugin.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,25 @@ def __init__(
9191

9292
def load(self, sirbot: Any) -> None:
9393
LOG.info("Loading slack plugin")
94-
self.api = SlackAPI(session=sirbot.http_session, token=self.token)
94+
# Store reference to sirbot for later initialization
95+
self._sirbot = sirbot
9596

9697
sirbot.router.add_route("POST", "/slack/events", endpoints.incoming_event)
9798
sirbot.router.add_route("POST", "/slack/commands", endpoints.incoming_command)
9899
sirbot.router.add_route("POST", "/slack/actions", endpoints.incoming_action)
99100

101+
# Initialize API after session is created
102+
sirbot.on_startup.append(self._initialize_api)
103+
100104
if self.bot_user_id and not self.bot_id:
101105
sirbot.on_startup.append(self.find_bot_id)
102106

107+
async def _initialize_api(self, app: Any) -> None:
108+
"""Initialize SlackAPI after http_session is created."""
109+
if self.api is None:
110+
LOG.info("Initializing Slack API client")
111+
self.api = SlackAPI(session=self._sirbot.http_session, token=self.token)
112+
103113
def on_event(self, event_type: str, handler: AsyncHandler, wait: bool = True) -> None:
104114
"""Register handler for an event."""
105115
handler = _ensure_async(handler)

tests/conftest.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,19 @@ async def bot() -> SirBot:
4444
b.load_plugin(airtable)
4545
b.load_plugin(api)
4646

47+
# Manually initialize the Slack API for tests that don't use aiohttp_client
48+
# (which would trigger startup callbacks automatically).
49+
# The _initialize_api method is idempotent, so it won't overwrite mocks.
50+
await slack._initialize_api(b)
51+
4752
yield b
4853

4954
# Cleanup session
5055
await b["http_session"].close()
5156

5257

5358
@pytest.fixture
54-
def slack_bot(bot: SirBot):
59+
async def slack_bot(bot: SirBot):
5560
slack = SlackPlugin(
5661
token="token",
5762
verify="supersecuretoken",
@@ -60,4 +65,6 @@ def slack_bot(bot: SirBot):
6065
)
6166
endpoints.slack.create_endpoints(slack)
6267
bot.load_plugin(slack)
68+
# Manually initialize the Slack API (idempotent, won't overwrite existing)
69+
await slack._initialize_api(bot)
6370
return bot

0 commit comments

Comments
 (0)