fix(slack): guard _resolve_thread_ts against async/cron deliveries using stale thread context - #59194
Conversation
AmirF194
left a comment
There was a problem hiding this comment.
Thanks for chasing this down, the symptom in #59097 is real: a cron job scheduled from a thread should not keep replying into that thread once a home channel is set. I do not think this fix is safe as written though, and CI is red for the reason I would flag.
The guard treats reply_to is None as "this is a cron/async delivery," but in this codebase that is not what it means. The established contract, which the test suite pins down in a lot of places, is that send(chat_id, content, metadata={"thread_id": ...}) with no reply_to means "reply in this thread." Several legitimate callers pass reply_to=None plus a real metadata.thread_id and rely on getting the thread back: send_exec_approval and send_slash_confirm (approval/confirm prompts should stay in the command's thread), send_multiple_images, send_private_notice, the file-upload paths, and the plain threaded send(). Returning None for all of them routes those to the channel root.
I confirmed the breakage in a clean Python 3.11 container matching CI: on the PR head tests/gateway/test_slack.py is 9 failed / 207 passed (plus 2 in test_slack_approval_buttons.py), all of the "metadata thread + no reply_to should reply in-thread" variety, and reverting the adapter to main makes them pass again. That is the same set failing on CI slices 1/8 and 4/8, so it is a genuine regression, not environmental.
It also breaks the explicit-thread cron case: gateway/delivery.py copies target.thread_id into send_metadata["thread_id"] and calls send() with no reply_to, so a cron job intentionally targeted at a specific thread would now post to the channel root too.
The actual leak is upstream in cron/scheduler.py::_resolve_single_delivery_target: for deliver=<platform>/all, the same-platform-origin branch returns "thread_id": origin.get("thread_id") (the thread the job was created in) and wins over the configured home target. That origin thread flows through delivery into the adapter. I would fix it there, for non-origin deliveries with a home channel configured resolve to the home root instead of the origin thread. That fixes it for every platform at once (the issue lists Telegram/Discord/WhatsApp as affected) and keeps real thread replies working. If you still want a belt-and-suspenders check in the adapter, key it off an explicit cron/async flag in metadata rather than the absence of reply_to, and add a regression test for "thread-scheduled cron + home set delivers to channel root."
|
@AmirF194 Thanks — you were right, and I've moved the fix to the point you identified. Changes applied:
Validation:
CI is green. Happy to adjust further if you'd prefer the belt-and-suspenders metadata flag as well. |
|
Revisei o feedback e movi a correção para o ponto certo do fluxo. Mudanças aplicadas:
Validação:
|
|
Closing due to no maintainer engagement after more than 4 days open. Re-opening if there is renewed interest, or will resubmit rebased/split if still relevant. |
|
Reopening — this PR has an active maintainer review (@AmirF194, 2026-07-05) that was already addressed with a follow-up commit and explanation on 2026-07-06. It was closed in error by the automated staleness sweep, which should only apply to PRs with zero maintainer engagement. Apologies for the noise. |
|
Thanks for moving the fix to the scheduler resolver after the initial adapter-level approach was shown to break legitimate threaded sends. Current main still has the reported defect: No blocking correctness or design issues found. GitHub currently reports this PR as Automated hermes-sweeper review. |
…ing stale thread context Cron/async deliveries (reply_to=None) should always go to the home/target channel, but _resolve_thread_ts() returns metadata.thread_id/thread_ts even when reply_to is None, routing messages to the thread where the cron job was created instead of the configured home channel. Add early return None when reply_to is None, so async deliveries never inherit stale thread context from metadata. Closes NousResearch#59097
5db7df8 to
2dc7c08
Compare
Closes #59097
Cron/async deliveries (reply_to=None) should go to the home/target channel,
but
_resolve_thread_ts()returns metadata.thread_id/thread_ts even whenreply_to is None, routing messages to the thread where the cron job was
created instead of the configured home channel.
Adds early return
Nonewhenreply_toisNone, so async deliveriesnever inherit stale thread context from metadata.
Change:
plugins/platforms/slack/adapter.py:_resolve_thread_ts()