fix(relay): promote the surviving reply_to anchor into metadata.thread_id on Slack sends (QA-7)

The connector's Slack sender threads on metadata ONLY: threadTs() reads
metadata.thread_id/thread_ts and never the frame's reply_to. base.py's
final-reply lane (and its stream-fallback 'first response' resend) builds
metadata from source.thread_id — None for a top-level DM — so its sends
carried reply_to as the sole threading signal and posted to the home
channel (2026-07-27 post-approval report; the 15:17:03 frame showed
meta_keys=['notify','user_id']).

After the QA-6 mode gate keeps the anchor, copy it into
metadata.thread_id so the wire carries the signal where the connector
reads it. Flat mode unaffected (anchor already nulled); explicit thread
metadata wins; non-Slack untouched.
This commit is contained in:
Victor Kyriazakos 2026-07-27 15:56:10 +00:00
parent 467534b43e
commit a51a17ebe3
2 changed files with 22 additions and 0 deletions

View file

@ -784,6 +784,24 @@ class RelayAdapter(BasePlatformAdapter):
)
if effective_reply_to is None and reply_to is not None:
send_metadata.pop("reply_to_message_id", None)
# QA-7: the connector's Slack sender THREADS ON METADATA ONLY —
# threadTs() reads metadata.thread_id/thread_ts and never looks at
# the frame's reply_to. A send whose only threading signal is
# reply_to (base.py's final-reply and fallback lanes build metadata
# from source.thread_id = None for a top-level DM) would post to the
# home channel even though _resolve_reply_to_for_send kept the
# anchor. Promote the surviving anchor into metadata.thread_id so
# the wire carries it where the connector actually reads it. Only
# when the mode gate kept the anchor (thread-per-message / real
# thread) — flat mode already nulled effective_reply_to above.
if (
effective_reply_to is not None
and self._platform_by_chat.get(str(chat_id)) == Platform.SLACK.value
and not (
send_metadata.get("thread_id") or send_metadata.get("thread_ts")
)
):
send_metadata["thread_id"] = str(effective_reply_to)
result = await self._transport.send_outbound(
{
"op": "send",

View file

@ -87,6 +87,10 @@ async def test_slack_dm_reply_keeps_anchor_in_thread_per_message_mode():
assert frame["reply_to"] == "1700.0001", (
"thread-per-message: the triggering ts anchors the final reply"
)
# QA-7: the connector's Slack sender threads on metadata.thread_id ONLY
# (threadTs() never reads the frame's reply_to), so the surviving anchor
# must be promoted into metadata for the send to actually thread.
assert (frame["metadata"] or {}).get("thread_id") == "1700.0001"
@pytest.mark.asyncio