Skip to content

fix(slack): preserve thread aliases in runtime outbound sends#62947

Merged
bek91 merged 2 commits intoopenclaw:mainfrom
bek91:codex/slack-direct-thread-alias
Apr 21, 2026
Merged

fix(slack): preserve thread aliases in runtime outbound sends#62947
bek91 merged 2 commits intoopenclaw:mainfrom
bek91:codex/slack-direct-thread-alias

Conversation

@bek91
Copy link
Copy Markdown
Contributor

@bek91 bek91 commented Apr 8, 2026

Summary

  • Problem: dep-backed direct sends that go through the generic runtime outbound wrapper could drop Slack thread context when callers passed threadTs instead of the generic threadId / messageThreadId fields.
  • Why it matters: subagent completion and other direct-delivery paths could post into the channel instead of the intended Slack thread.
  • What changed: the generic runtime sender now treats Slack-style threadTs as a fallback alias for the existing thread resolver, while preserving canonical precedence messageThreadId -> threadId -> threadTs.
  • What did NOT change (scope boundary): no Slack transport behavior changed beyond alias forwarding, and no gateway, schema, or non-thread delivery behavior was modified.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: the generic runtime outbound sender only resolved canonical thread fields and did not fall back to Slack's threadTs alias when constructing the outbound send context.
  • Missing detection / guardrail: there was no focused regression test covering the dep-backed runtime send path with Slack alias inputs.
  • Contributing context (if known): current main already had generalized threadId / replyToId helpers, so the rebased fix only needed to extend thread resolution to include threadTs.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/cli/send-runtime/channel-outbound-send.test.ts
  • Scenario the test should lock in: when runtime callers pass threadTs, the wrapper forwards that value as the outbound threadId, and canonical fields still win when both forms are present.
  • Why this is the smallest reliable guardrail: the bug is in the generic runtime send wrapper's option normalization, so a focused unit test on that wrapper isolates the failing behavior without needing a live Slack transport.
  • Existing test that already covers this (if any): none before this change.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Slack-threaded direct sends that go through the generic runtime wrapper now stay in the intended thread when the caller supplies threadTs.

Diagram (if applicable)

Before:
[runtime caller passes threadTs] -> [generic wrapper ignores alias] -> [Slack send without thread context]

After:
[runtime caller passes threadTs] -> [generic wrapper resolves threadTs as fallback thread id] -> [Slack send stays in thread]

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local OpenClaw repo checkout
  • Model/provider: N/A
  • Integration/channel (if any): Slack direct-send path, verified via unit coverage
  • Relevant config (redacted): none required beyond test defaults

Steps

  1. Call the generic runtime outbound sender with Slack-targeted delivery options and threadTs set.
  2. Inspect the outbound adapter call arguments.
  3. Repeat with canonical thread fields also present.

Expected

  • threadTs is forwarded as the effective outbound thread id when no canonical thread field is set.
  • Canonical fields still take precedence when both forms are present.

Actual

  • Before the fix, the wrapper dropped the Slack alias and lost thread context.
  • After the fix, the focused CLI Vitest lane passes and confirms the expected forwarding and precedence behavior.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Focused verification run after rebase:

node scripts/run-vitest.mjs run --config test/vitest/vitest.cli.config.ts src/cli/send-runtime/channel-outbound-send.test.ts

Result: 1 test file passed, 6 tests passed.

Human Verification (required)

What I personally verified (not just CI), and how:

  • Verified scenarios: rebased the branch onto current main, resolved conflicts against the newer runtime-send helpers, and ran the focused CLI Vitest file covering the changed behavior.
  • Edge cases checked: canonical precedence over aliases, Slack threadTs fallback, and preservation of existing direct-send media/text behavior in the same test file.
  • What I did not verify: live Slack delivery against a real workspace, and a clean full-repo pnpm check run because current-tree unrelated failures remain in extensions/discord and extensions/qa-lab.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Note: the Greptile thread is addressed in code on the rebased branch. I have not resolved the GitHub thread yet.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: another caller may rely on a non-canonical thread alias behavior not covered here.
    • Mitigation: the resolver remains precedence-preserving and the added tests lock the alias handling to the existing generic thread-id surface.

AI-Assisted

  • This PR was updated with AI assistance using Codex.
  • Testing level: lightly tested.
  • Local codex review --base origin/main was run on the rebased diff and did not find a diff-specific regression.
  • Session log / prompts: available from the Codex session used to prepare the rebase and validation steps if maintainers want them.
  • I understand the code being submitted and validated the final rebased diff before pushing.

@openclaw-barnacle openclaw-barnacle Bot added cli CLI command changes size: S labels Apr 8, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 8, 2026

Greptile Summary

This PR fixes dropped thread context in the generic runtime outbound sender when Slack callers supply threadTs/replyToId aliases instead of the canonical messageThreadId/replyToMessageId fields. The fix adds the two alias fields to RuntimeSendOpts and uses ?? chaining so canonical fields still take precedence, with focused Vitest coverage for all three alias-mapping paths.

Confidence Score: 5/5

Safe to merge — the fix is correct, canonical precedence is preserved, and the new tests cover all three alias-mapping scenarios.

Only a P2 style finding (double-evaluation of a null-coalescing expression). No logic errors, no missing edge-case handling, and backward compatibility is intact.

No files require special attention.

Vulnerabilities

No security concerns identified.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/cli/send-runtime/channel-outbound-send.ts
Line: 40-42

Comment:
**Double evaluation of null-coalescing expression**

The `opts.replyToMessageId ?? opts.replyToId` expression is evaluated twice — once for the null-check and once inside `String(...)`. While safe here (simple property reads), extracting it to a local avoids the repetition and makes the intent clearer.

```suggestion
        const resolvedReplyTo = opts.replyToMessageId ?? opts.replyToId;
        replyToId:
          resolvedReplyTo == null
            ? undefined
            : normalizeOptionalString(String(resolvedReplyTo)),
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(slack): preserve direct send thread ..." | Re-trigger Greptile

Comment on lines +40 to +42
(opts.replyToMessageId ?? opts.replyToId) == null
? undefined
: normalizeOptionalString(String(opts.replyToMessageId)),
: normalizeOptionalString(String(opts.replyToMessageId ?? opts.replyToId)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Double evaluation of null-coalescing expression

The opts.replyToMessageId ?? opts.replyToId expression is evaluated twice — once for the null-check and once inside String(...). While safe here (simple property reads), extracting it to a local avoids the repetition and makes the intent clearer.

Suggested change
(opts.replyToMessageId ?? opts.replyToId) == null
? undefined
: normalizeOptionalString(String(opts.replyToMessageId)),
: normalizeOptionalString(String(opts.replyToMessageId ?? opts.replyToId)),
const resolvedReplyTo = opts.replyToMessageId ?? opts.replyToId;
replyToId:
resolvedReplyTo == null
? undefined
: normalizeOptionalString(String(resolvedReplyTo)),
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/send-runtime/channel-outbound-send.ts
Line: 40-42

Comment:
**Double evaluation of null-coalescing expression**

The `opts.replyToMessageId ?? opts.replyToId` expression is evaluated twice — once for the null-check and once inside `String(...)`. While safe here (simple property reads), extracting it to a local avoids the repetition and makes the intent clearer.

```suggestion
        const resolvedReplyTo = opts.replyToMessageId ?? opts.replyToId;
        replyToId:
          resolvedReplyTo == null
            ? undefined
            : normalizeOptionalString(String(resolvedReplyTo)),
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@bek91 bek91 force-pushed the codex/slack-direct-thread-alias branch from 512f332 to a432bd8 Compare April 20, 2026 04:57
Copy link
Copy Markdown
Contributor

Codex review: this looks good to me. The threadTs fallback and precedence tests match the runtime outbound-send behavior, and I do not see a code blocker here.

Only current blocker is CI: the red checks are in BlueBubbles extension lanes, which look unrelated to this PR's touched files. Please rebase/rerun; if it comes back green, I think this is mergeable.

@bek91 bek91 force-pushed the codex/slack-direct-thread-alias branch from a432bd8 to 6d561df Compare April 21, 2026 04:00
@bek91 bek91 merged commit 49b233c into openclaw:main Apr 21, 2026
91 checks passed
steipete pushed a commit that referenced this pull request Apr 22, 2026
Slack-threaded direct sends that go through the generic runtime wrapper now stay in the intended thread when the caller supplies threadTs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants