fix(slack): harden thread continuation gating#75630
fix(slack): harden thread continuation gating#75630soichiyo wants to merge 6 commits intoopenclaw:mainfrom
Conversation
|
Codex review: needs maintainer review before merge. Summary Reproducibility: yes. Source inspection on current main shows a Slack reply with Real behavior proof Next step before merge Security Review detailsBest possible solution: Land this PR after maintainer review and exact-head validation, keeping the fix scoped to Slack thread resolution, prepare-time ambiguity dropping, and compatibility mention-gating helpers. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection on current main shows a Slack reply with Is this the best way to solve the issue? Yes. Marking unresolved resolver results as ambiguous and dropping them in Slack prepare is the narrowest maintainable fix, and it avoids reintroducing the unreliable What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 83aad863fd77. Re-review progress:
|
de52097 to
91894a0
Compare
martingarramon
left a comment
There was a problem hiding this comment.
Static review (no fresh checkout / pnpm test). Diff reads clean against the stated goal. A few notes:
Tie to #75969: the user symptom there ("thread message arrives into channel root") matches the new _ambiguousThreadReply → return null path in extensions/slack/src/monitor/message-handler/prepare.ts:493-505 exactly. Worth surfacing in the PR body for an extra close-loop.
One out-of-scope change: extensions/slack/src/monitor/message-handler/prepare.ts:612 drops as AckReactionScope | undefined to as AckReactionScope. Unrelated to the PR title — separate cleanup, or upstream guarantee change?
Possible double-count: src/channels/mention-gating.ts:205-212 adds bot_thread_participant via threadContinuation, but the call site in extensions/slack/src/monitor/message-handler/prepare.ts still passes it directly via implicitMentionKinds. If both fire, matchedKinds could list the kind twice — a test for this case or a call-site dedupe is recommended.
logVerbose guard drops in thread-resolution.ts: safe — logVerbose self-gates at src/globals.ts:10-23. No log-spam regression.
CI red: the 10 failures look PR-base-stale — extensions/matrix/src/matrix/monitor/handler.ts:1834-1835 errors (liveDmAllowFrom, dmScope-on-wrong-type) don't reproduce on current origin/main; matrix-side commits 7e8d95b413 and f6a1d70080 likely settled the typing. A rebase + push should green most of them.
CI will be authoritative; the read above spans every Slack-side branch I can reason about. Since src/channels/mention-gating.ts is shared infra, would also want regression coverage on at least one other channel (Discord/Telegram) before relying on threadContinuation cross-channel.
b9d3b13 to
7c1b2fa
Compare
|
Thanks for the review! I updated the PR body to call out the #75969 symptom explicitly. On the The Focused tests pass on the rebased head: |
…ase 1) Thread continuation in Slack channels with requireMention was fragile because participation state lived only in a 24-hour in-memory cache that was lost on process restart. This caused threads to silently drop after gateway updates. Phase 1 changes: - Add persistent thread participation store (JSON file alongside session store) so restart no longer loses thread continuation - Add structured diagnostic logs at mention gating, thread continuation decisions, and participation recording to make silent failures traceable - Elevate thread_ts resolution failure from verbose-only to always-visible warning since it causes silent degradation - Pass storePath through PreparedSlackMessage so dispatch can persist participation without re-resolving the path Note: pre-commit tsgo blocked by pre-existing matrix test errors (5 errors in extensions/matrix/ unrelated to this change). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on gating (Phase 2) Phase 2A — thread_ts resolution improvement: - Add conversations.replies as bounded fallback when conversations.history fails to resolve thread_ts for messages with parent_user_id - Mark unresolvable messages as _ambiguousThreadReply instead of silently treating them as root messages - Log ambiguous thread reply status in mention skip reasons Phase 2B — gating separation: - Split implicitMention into two distinct signals: - implicitMention: bot is the thread parent (root-message signal) - threadContinuation: bot has previously replied (participation signal) - Add threadContinuation as a first-class parameter to mention gating so root-message gating and thread-continuation gating are independently visible in logs and code - Backward compatible: threadContinuation is optional, existing callers (Teams, Google Chat, etc.) are unaffected Note: pre-commit tsgo blocked by pre-existing matrix test errors (5 errors in extensions/matrix/ unrelated to this change). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7c1b2fa to
1573a99
Compare
This is a follow-up to #58731 after the SDK-backed Slack thread participation persistence landed on main. I rebased onto the current state-store path, dropped the competing JSON participation store from this branch, and removed the unrelated Dockerfile assertion from the original branch.
This also closes the loop on the symptom in #75969: unresolved Slack thread replies are now marked ambiguous and skipped before they can be routed as root channel messages.
Changes:
conversations.repliesfallback path_ambiguousThreadReplythreadContinuationthrough the legacy mention-gating helpersValidation:
pnpm test extensions/slack/src/sent-thread-cache.test.ts extensions/slack/src/monitor/message-handler/prepare.test.ts extensions/slack/src/monitor/monitor.thread-resolution.test.ts extensions/slack/src/monitor.threading.missing-thread-ts.test.ts src/channels/mention-gating.test.tsReal behavior proof
parent_user_idbut no resolvablethread_tsshould not continue downstream as ordinary root channel messages. The resolver should mark them ambiguous, including the cached-null path, soprepareSlackMessagecan drop them before root routing.openclaw/openclaw@31a710c5a6, running the production Slack thread resolver from this PR head (1573a992afdaa87e382681a7f65416f2a66dc80d) with Nodev25.9.0.{ "first": { "thread_ts": null, "ambiguous": true }, "second": { "thread_ts": null, "ambiguous": true }, "historyCalls": 1 }ambiguous: truewith nothread_ts, and the history lookup is only called once. The focused prepare-path regression also verifies that a message carrying_ambiguousThreadReplyreturnsnullinstead of being routed as a root message.