Skip to content

fix: harden routing/session isolation for followups and heartbeat#25881

Merged
steipete merged 1 commit intomainfrom
fix/routing-session-isolation-25864-25835-25730
Feb 24, 2026
Merged

fix: harden routing/session isolation for followups and heartbeat#25881
steipete merged 1 commit intomainfrom
fix/routing-session-isolation-25864-25835-25730

Conversation

@steipete
Copy link
Contributor

@steipete steipete commented Feb 24, 2026

Summary

  • stop heartbeat-mode target resolution from inheriting session lastThreadId unless a thread/topic is explicit
  • use originating channel context (not synthetic provider tags) for messaging-tool duplicate suppression and embedded run channel hints
  • preserve queued overflow summary routing metadata (originatingChannel, originatingTo, originatingThreadId) during drain
  • enforce strict followup origin isolation by not falling back to the active dispatcher when explicit origin route delivery fails
  • add regression coverage across outbound target resolution, heartbeat context wiring, followup routing, payload dedupe, and queue drain routing

Tests

  • pnpm check
  • pnpm build
  • pnpm test

Fixes #25864
Fixes #25835
Fixes #25730

Thanks @Gamedesigner, @jadeathena84-arch, and @markshields-tl for the reports and analysis.

Mintlify

0 threads from 0 users in Mintlify

  • No unresolved comments

Open in Mintlify Editor

Greptile Summary

This PR hardens routing and session isolation across heartbeat, followup, and messaging tool contexts to prevent cross-channel message leakage. The changes ensure that heartbeat/cron deliveries don't inherit cached thread IDs unless explicitly requested, followup routing strictly enforces origin isolation without dispatcher fallback on failure, and messaging tool deduplication uses originating channel context instead of synthetic provider tags. Queue drain logic now preserves routing metadata (originatingChannel, originatingTo, originatingAccountId, originatingThreadId) during overflow summary generation.

Key improvements:

  • Heartbeat mode stops inheriting lastThreadId from session state (src/infra/outbound/targets.ts:121)
  • Followup runner removes dispatcher fallback when explicit origin routing fails (src/auto-reply/reply/followup-runner.ts:101-105)
  • Messaging tool dedupe prefers OriginatingChannel over synthetic Provider tags (src/auto-reply/reply/agent-runner-payloads.ts:90, src/auto-reply/reply/followup-runner.ts:259)
  • Queue drain preserves routing metadata for overflow summaries (src/auto-reply/reply/queue/drain.ts:119-122)
  • Heartbeat runner wires delivery context into session context (src/infra/heartbeat-runner.ts:666-669)

Test coverage includes regression tests for all four main fixes plus the overflow summary routing preservation.

Confidence Score: 5/5

  • This PR is safe to merge with no critical issues identified
  • The changes are well-structured with clear intent, comprehensive test coverage for all four bug fixes, and consistent application of the isolation pattern across multiple files. The logic correctly handles edge cases (e.g., checking mode !== "heartbeat" before inheriting threadId, preserving routing metadata during queue drain). No security concerns, breaking changes, or logical errors detected.
  • No files require special attention

Last reviewed commit: e1607a8

@steipete steipete self-assigned this Feb 24, 2026
@cursor
Copy link

cursor bot commented Feb 24, 2026

PR Summary

Medium Risk
Touches followup/heartbeat routing and dedupe logic across multiple paths; while well-covered by tests, mistakes could misroute or drop replies in production.

Overview
Prevents cross-channel reply leakage in shared sessions by tightening followup routing: followups routed to an explicit originating channel no longer fall back to the active dispatcher when routing fails, and queue overflow summary followups now retain originatingChannel/originatingTo/originatingAccountId/originatingThreadId metadata.

Improves outbound and dedupe correctness for synthetic providers (heartbeat/cron/exec-event) by preferring OriginatingChannel over Provider for embedded run context and messaging-tool duplicate suppression, and stops heartbeat-mode delivery target resolution from inheriting cached session lastThreadId unless a thread/topic is explicitly requested. Adds regression tests for these scenarios and updates CHANGELOG.md accordingly.

Written by Cursor Bugbot for commit 1bf6bab. This will update automatically on new commits. Configure here.

@openclaw-barnacle openclaw-barnacle bot added the maintainer Maintainer-authored PR label Feb 24, 2026
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@steipete steipete force-pushed the fix/routing-session-isolation-25864-25835-25730 branch from e1607a8 to 1bf6bab Compare February 24, 2026 23:18
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e1607a8500

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 117 to +120
run,
enqueuedAt: Date.now(),
originatingChannel: item.originatingChannel,
originatingTo: item.originatingTo,

Choose a reason for hiding this comment

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

P1 Badge Use dequeued run context for overflow summary followups

When dropPolicy: "summarize" and cap > 1, this branch drains one queued item but still uses queue.lastRun instead of that item’s own run. After this change, the drained item’s originatingChannel/originatingTo are forwarded, so replies can now be routed to channel A while the embedded run context (messageProvider, auth/account/model selection) comes from the most recently enqueued channel B. In mixed-channel shared sessions this can produce cross-channel context leakage or wrong delivery behavior; the run context and origin metadata need to stay aligned per dequeued item.

Useful? React with 👍 / 👎.

@steipete steipete merged commit ccbeb33 into main Feb 24, 2026
25 of 27 checks passed
@steipete steipete deleted the fix/routing-session-isolation-25864-25835-25730 branch February 24, 2026 23:20
@steipete
Copy link
Contributor Author

/landpr landed on main.

What landed:

Validation notes:

  • Focused regression suites passed locally on the rebased head.
  • Full local gate after rebase showed existing tmp-root assertion failures outside this diff (src/line/download.test.ts, extensions/feishu/src/media.test.ts).

Thanks again to reporters: @Gamedesigner, @jadeathena84-arch, and @markshields-tl.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR size: M

Projects

None yet

1 participant