feat: Make error slack messages only visable to user (#1018)

This commit is contained in:
Brace Sproul 2026-03-05 13:00:23 -08:00 • committed by GitHub
parent eb47c86c80
commit 659dd71433
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 121 additions and 5 deletions

View file

@ -14,7 +14,7 @@ from langgraph_sdk import get_client
from ..encryption import encrypt_token
from .linear import comment_on_linear_issue
from .slack import post_slack_thread_reply
from .slack import post_slack_ephemeral_message, post_slack_thread_reply
logger = logging.getLogger(__name__)
@ -206,7 +206,33 @@ async def leave_failure_comment(
slack_thread = configurable.get("slack_thread", {})
channel_id = slack_thread.get("channel_id") if isinstance(slack_thread, dict) else None
thread_ts = slack_thread.get("thread_ts") if isinstance(slack_thread, dict) else None
triggering_user_id = (
slack_thread.get("triggering_user_id") if isinstance(slack_thread, dict) else None
)
if channel_id and thread_ts:
if isinstance(triggering_user_id, str) and triggering_user_id:
logger.info(
"Posting auth failure ephemeral reply to Slack user %s in channel %s thread %s",
triggering_user_id,
channel_id,
thread_ts,
)
sent = await post_slack_ephemeral_message(
channel_id=channel_id,
user_id=triggering_user_id,
text=message,
thread_ts=thread_ts,
)
if sent:
return
logger.warning(
"Failed to post ephemeral auth failure reply for Slack user %s; falling back to thread reply",
triggering_user_id,
)
else:
logger.warning(
"Missing Slack triggering_user_id for auth failure reply; falling back to thread reply",
)
logger.info(
"Posting auth failure reply to Slack channel %s thread %s",
channel_id,

View file

@ -202,6 +202,39 @@ async def post_slack_thread_reply(channel_id: str, thread_ts: str, text: str) ->
return False
async def post_slack_ephemeral_message(
channel_id: str, user_id: str, text: str, thread_ts: str | None = None
) -> bool:
"""Post an ephemeral message visible only to one user."""
if not SLACK_BOT_TOKEN:
return False
payload: dict[str, str] = {
"channel": channel_id,
"user": user_id,
"text": text,
}
if thread_ts:
payload["thread_ts"] = thread_ts
async with httpx.AsyncClient() as http_client:
try:
response = await http_client.post(
f"{SLACK_API_BASE_URL}/chat.postEphemeral",
headers=_slack_headers(),
json=payload,
)
response.raise_for_status()
data = response.json()
if not data.get("ok"):
logger.warning("Slack chat.postEphemeral failed: %s", data.get("error"))
return False
return True
except httpx.HTTPError:
logger.exception("Slack chat.postEphemeral request failed")
return False
async def add_slack_reaction(channel_id: str, message_ts: str, emoji: str = "eyes") -> bool:
"""Add a reaction to a Slack message."""
if not SLACK_BOT_TOKEN:

View file

@ -12,19 +12,76 @@ def test_leave_failure_comment_posts_to_slack_thread(
) -> None:
called: dict[str, str] = {}
async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, message: str) -> bool:
async def fake_post_slack_ephemeral_message(
channel_id: str, user_id: str, text: str, thread_ts: str | None = None
) -> bool:
called["channel_id"] = channel_id
called["user_id"] = user_id
called["thread_ts"] = thread_ts
called["message"] = message
called["message"] = text
return True
async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, message: str) -> bool:
raise AssertionError("post_slack_thread_reply should not be called when ephemeral succeeds")
monkeypatch.setattr(auth, "post_slack_ephemeral_message", fake_post_slack_ephemeral_message)
monkeypatch.setattr(auth, "post_slack_thread_reply", fake_post_slack_thread_reply)
monkeypatch.setattr(
auth,
"get_config",
lambda: {"configurable": {"slack_thread": {"channel_id": "C123", "thread_ts": "1.2"}}},
lambda: {
"configurable": {
"slack_thread": {
"channel_id": "C123",
"thread_ts": "1.2",
"triggering_user_id": "U123",
}
}
},
)
asyncio.run(auth.leave_failure_comment("slack", "auth failed"))
assert called == {"channel_id": "C123", "thread_ts": "1.2", "message": "auth failed"}
assert called == {
"channel_id": "C123",
"user_id": "U123",
"thread_ts": "1.2",
"message": "auth failed",
}
def test_leave_failure_comment_falls_back_to_slack_thread_when_ephemeral_fails(
monkeypatch: pytest.MonkeyPatch,
) -> None:
thread_called: dict[str, str] = {}
async def fake_post_slack_ephemeral_message(
channel_id: str, user_id: str, text: str, thread_ts: str | None = None
) -> bool:
return False
async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, message: str) -> bool:
thread_called["channel_id"] = channel_id
thread_called["thread_ts"] = thread_ts
thread_called["message"] = message
return True
monkeypatch.setattr(auth, "post_slack_ephemeral_message", fake_post_slack_ephemeral_message)
monkeypatch.setattr(auth, "post_slack_thread_reply", fake_post_slack_thread_reply)
monkeypatch.setattr(
auth,
"get_config",
lambda: {
"configurable": {
"slack_thread": {
"channel_id": "C123",
"thread_ts": "1.2",
"triggering_user_id": "U123",
}
}
},
)
asyncio.run(auth.leave_failure_comment("slack", "auth failed"))
assert thread_called == {"channel_id": "C123", "thread_ts": "1.2", "message": "auth failed"}