Skip to content

fix(integration-events): suppress self-bot-user echo of own Slack writebacks#177

Merged
khaliqgant merged 3 commits into
mainfrom
fix/integration-event-self-echo-suppression
Jun 9, 2026
Merged

fix(integration-events): suppress self-bot-user echo of own Slack writebacks#177
khaliqgant merged 3 commits into
mainfrom
fix/integration-event-self-echo-suppression

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 8, 2026

Copy link
Copy Markdown
Member

⚠️ DRAFT / HOLD-FOR-MERGE — operator is asleep; explicit no-merge hold. Stacked on the (also-held) #175 branch fix/integration-event-dedup-logical-identity, so this must not merge before #175. The hold stands regardless of any Slack relay delay.

Summary

  • Suppress Slack materialized echoes of Pear's own outbound writeback posts by learning the workspace bot Slack user id from a recent outbound writeback text match.
  • Capture outbound Slack writeback command text from the local mount watcher before the existing command-file notification suppression path returns.
  • Add a dedicated self-echo suppression telemetry counter and a regression covering own bot, human, and unrelated bot delivery behavior.

Design

Pear does not receive a returned Slack message ts from the writeback command file path; the local command file contains only the outbound { "text": ... }. Because discovery lists Pear's bot user indistinguishably from other Slack bots, user_is_bot alone is not a valid suppression signal.

This uses a scoped bootstrap correlation instead:

  • local Slack writeback command files record a per-project key of scope:textHash, where scope is the canonical channel id or DM/user id and text is trimmed with collapsed whitespace before hashing;
  • the first materialized Slack event only learns a self bot user id when user_is_bot === true, an author id exists, and the same scope/text key was recently recorded;
  • once learned, that author id is authoritative for the project until its TTL expires, so later messages from other bot ids are not learned or suppressed just because their text matches;
  • suppressed self-echoes return before any dedupe claim is made.

Bootstrap caveat: the very first echo before Pear has captured any outbound writeback text cannot be suppressed because there is nothing safe to correlate yet. After the first correlated echo learns the bot id, later messages from that id are suppressed directly.

Test Plan

  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts (75/75 pass)
  • npx tsc --noEmit

Regression Coverage

The new bridge regression verifies:

  • own bot U0B2596R7EZ / file_by_agent_relay echo is suppressed after outbound writeback capture (text-correlation branch);
  • a second U0B2596R7EZ message with non-matching text is still suppressed (authoritative learned-id branch);
  • a human user posting the same text is still injected;
  • a different bot (coderabbit) with nonmatching text is still injected.

Reviewer-noted caveats (non-blocking; shadow review by skills-fixer + lead review)

  • Residual mis-learn window: if a different is_bot author posts our exact writeback text in the same scope within the 15-min outbound window before we ever observe our own first echo (learned-id cache still empty), it could mis-learn that bot's id and suppress it for the 1h self-id TTL. Probability is very low — our own echo returns within seconds and is almost always the first match — and this is inherent to text being the only correlation signal pear has. The 1h TTL self-heals a bad correlation. Documented, not fixed.
  • Persistence: the learned-id cache is in-memory per session; cross-restart persistence is a clean follow-up (after a restart the bootstrap window re-applies once per scope).
  • Test coverage gap (follow-up): the regression taps recordSlackOutboundWriteback() directly, so the watchLocalMountsonSlackOutboundWriteback file-watch wiring and the isSlackLocalWritebackCommandPath predicate are not exercised end-to-end. Worth an integration-level test as a follow-up.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

… trees

A Slack thread PARENT materializes under BOTH the flat messages/<ts> record
AND the threads/<ts> root (thread_ts == parent ts) — two distinct files with
distinct revisions, one logical message. slackLogicalChangeFingerprint keyed the
threads-root copy as `...:thread:<ts>:...` and the messages copy as
`...:message:<ts>:...`, so the two copies produced different injection-dedupe
keys and the same message was injected twice (and re-injected as each tree's
record was re-committed). Operator observed the same message echoing into the
channel repeatedly (one msg logged 59x received / 3x injected).

Fix: collapse the thread ROOT (no reply segment) to the message identity so both
materializations share one dedupe key and inject once. Thread REPLIES keep their
distinct `:reply:<ts>` identity. Regression test added; 74/74 bridge tests pass.

Note: an agent still receives ONE copy of its own outbound post (mirror reads it
back down); fully suppressing self-echo needs a self-bot-user signal — follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 81abd7f7-ecb1-47a2-89b4-9d734337398f

📥 Commits

Reviewing files that changed from the base of the PR and between b67c201 and 4c6259e.

📒 Files selected for processing (3)
  • src/main/__tests__/integration-event-bridge.test.ts
  • src/main/integration-event-bridge.ts
  • src/shared/types/ipc.ts

📝 Walkthrough

Walkthrough

This PR extends the integration event bridge to suppress Slack "self-echo" events from outbound writebacks by tracking correlation keys and bot identity, refines thread-root deduplication fingerprinting, and adds comprehensive integration tests with telemetry coverage.

Changes

Slack Self-Echo Suppression and Deduplication

Layer / File(s) Summary
Telemetry counter type and initialization
src/shared/types/ipc.ts, src/main/integration-event-bridge.ts
IntegrationEventTelemetryCounters gains eventsSelfEchoSuppressed: number field; per-project telemetry counters initialize this new counter to 0.
Text normalization and correlation key computation
src/main/integration-event-bridge.ts
Helper functions normalize outbound Slack text, compute stable short hashes, derive scope keys from paths, and compute correlation keys from scope + hashed text for writeback matching.
Author and bot identity extraction
src/main/integration-event-bridge.ts
Preview-based helpers extract Slack user IDs and detect bot flags from event context; writeback command path detection and outbound text parsing from JSON buffers.
Slack writeback command watching infrastructure
src/main/integration-event-bridge.ts
New Slack writeback root mapper, SlackOutboundWritebackCommand type carrying local and remote paths, TTL constant (SLACK_SELF_ECHO_WRITEBACK_TTL_MS), and watcher that monitors local Slack command roots and invokes callbacks on file changes.
TTL map management and self-echo suppression predicate
src/main/integration-event-bridge.ts
Per-project TTL maps track learned self/bot author user IDs and recent writeback correlation keys; TTL pruning utilities and shouldSuppressSlackSelfEcho() predicate detect and suppress self-echoes while incrementing eventsSelfEchoSuppressed and logging reasons.
Integration into event reconciliation and injection
src/main/integration-event-bridge.ts
Writeback command watcher registered in reconcile() to record outbound writebacks; close() clears TTL maps; injectEvent() adds early return when shouldSuppressSlackSelfEcho() returns true.
Thread-root logical deduplication
src/main/integration-event-bridge.ts
Slack fingerprinting adjusted so thread-root records (without reply segment) collapse onto the same logical message identity as channel top-level messages/<thread>/... materialization.

Test Harness and Coverage

Layer / File(s) Summary
Test harness workspace configuration
src/main/__tests__/integration-event-bridge.test.ts
Add homedir import; optional localMountWorkspaceId test configuration parameter wired into IntegrationEventBridge workspace handle with fallback to previous hardcoded value.
Deduplication integration tests
src/main/__tests__/integration-event-bridge.test.ts
New test emits Slack thread-parent record materialized under both messages/<ts> and threads/<ts> roots, asserting single injection for the logically shared message and separate injection for subsequent replies.
Self-echo suppression integration test
src/main/__tests__/integration-event-bridge.test.ts
New test creates local Slack mount, writes outbound draft file, waits for writebacks to register, emits Slack change events for bot-authored echoes and other messages, asserts only non-self messages inject, and validates eventsSelfEchoSuppressed: 2 and eventsDropped: 0.
Telemetry snapshot assertion updates
src/main/__tests__/integration-event-bridge.test.ts
Six existing telemetry snapshot assertions updated to include eventsSelfEchoSuppressed: 0 counter.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • AgentWorkforce/pear#147: Modifies the same integration-event-bridge.ts deduplication and fingerprinting logic for Slack thread handling and dedup lifecycle.

Possibly related PRs

  • AgentWorkforce/pear#159: Also modifies Slack writeback handling during reconcile() for local command/draft roots in the same bridge module.
  • AgentWorkforce/pear#157: Overlaps at the same Slack event injection/deduplication flow paths and telemetry handling.

Poem

🐰 A writeback's whisper, now silent and true—
Self-echoes suppressed, no longer askew.
Threads fold together, one message takes flight,
Tests verify dedup and telemetry's light.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/integration-event-self-echo-suppression

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@khaliqgant khaliqgant force-pushed the fix/integration-event-self-echo-suppression branch from a8b58d8 to e62cd0d Compare June 8, 2026 20:58
@agent-relay-code

Copy link
Copy Markdown
Contributor

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Implemented one fix: updated the renderer IPC mock telemetry snapshot to include eventsSelfEchoSuppressed, matching the shared IntegrationEventTelemetryCounters type in ipc-mock.ts.

Local verification run:

  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts passed
  • npm test -- --test-name-pattern "slack self-bot writeback echoes" passed
  • npm run build:web passed after npm install

Addressed comments

  • gemini-code-assist[bot]: raised only a daily quota warning; no code issue to fix.
  • coderabbitai[bot]: review was skipped because the PR was draft; no actionable review comments or inline threads were present.

I did not print READY because I could not verify GitHub-side CI completion or mergeability from the available checkout.

agent-relay-code Bot added a commit that referenced this pull request Jun 8, 2026
@agent-relay-code

Copy link
Copy Markdown
Contributor

Implemented and locally verified the PR fixes.

I fixed the stale renderer mock breakage from the new telemetry counter and tightened Slack self-echo learning so a same-text post from another bot is not mislearned as Pear’s relay bot. The regression now covers that collision case before confirming the real file_by_agent_relay echo is suppressed.

Validation run:

  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm test -- --test-name-pattern "slack self-bot writeback echoes"
  • npm run build:web
  • npm run build

Addressed comments

  • gemini-code-assist[bot]: raised only a daily quota warning; no code issue to fix.
  • coderabbitai[bot]: review was skipped because the PR was draft; no actionable review comments or inline threads were present.
  • agent-relay-code[bot]: raised missing eventsSelfEchoSuppressed in the renderer IPC mock; fixed in ipc-mock.ts.
  • Senior reviewer finding: same-text Slack posts from another bot could be learned as the self bot before the real echo arrived; fixed in integration-event-bridge.ts and integration-event-bridge.ts, with regression coverage in integration-event-bridge.test.ts.

I did not print READY because I could not verify GitHub-side mergeability and required CI completion from this checkout.

@agent-relay-code

Copy link
Copy Markdown
Contributor

⚠️ pr-reviewer push failed (exit 1) — fixes were not applied to the PR. The notes below are advisory and were not pushed.

Implemented the validated fixes for PR #177.

Changed:

  • Added eventsSelfEchoSuppressed to the renderer IPC mock telemetry snapshot.
  • Prevented Slack self-bot learning from misclassifying arbitrary bots that post the same text by requiring the known Pear relay bot author name before learning the self bot user id.
  • Extended the regression test so a same-text CodeRabbit bot message is delivered before the real file_by_agent_relay echo is suppressed.

Verified:

  • node --experimental-strip-types --no-warnings --test --test-name-pattern "slack self-bot writeback echoes" src/main/__tests__/integration-event-bridge.test.ts
  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm run build:web
  • npm run build

Addressed comments

  • gemini-code-assist[bot]: raised only a daily quota warning; no code issue to fix.
  • coderabbitai[bot]: review was skipped because the PR was draft; no actionable review comments or inline threads were present.
  • agent-relay-code[bot] issuecomment-4653538667: raised missing eventsSelfEchoSuppressed in the renderer IPC mock; fixed in src/renderer/src/lib/ipc-mock.ts:627.
  • agent-relay-code[bot] issuecomment-4653596453: noted same-text Slack posts from another bot could be learned as Pear’s self bot before the real echo; fixed in src/main/integration-event-bridge.ts:63, src/main/integration-event-bridge.ts:1784, and src/main/integration-event-bridge.ts:2741, with regression coverage in src/main/__tests__/integration-event-bridge.test.ts:2348.

I did not print READY because I could not verify GitHub-side required CI completion or mergeability from the available tools.

agent-relay-code Bot added a commit that referenced this pull request Jun 8, 2026
@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer applied fixes — committed and pushed 96964b4 to this PR. The notes below describe what changed.

Implemented one additional validated fix: Slack self-echo suppression now treats a parsed bot_profile as a bot signal, so Pear’s relay echo is suppressed even when Slack metadata lacks user_is_bot. Regression coverage now exercises that shape.

Verified:

  • node --experimental-strip-types --no-warnings --test --test-name-pattern "slack self-bot writeback echoes" src/main/__tests__/integration-event-bridge.test.ts
  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm run build
  • npm run build:web

Addressed comments

  • gemini-code-assist[bot]: raised only a daily quota warning; no code issue to fix.
  • coderabbitai[bot]: review was skipped because the PR was draft; no actionable review comments or inline threads were present.
  • agent-relay-code[bot] issuecomment-4653538667: raised missing eventsSelfEchoSuppressed in the renderer IPC mock; already fixed in src/renderer/src/lib/ipc-mock.ts:627.
  • agent-relay-code[bot] issuecomment-4653596453: noted same-text Slack posts from another bot could be learned as Pear’s self bot before the real echo; already fixed by the relay author-name guard in src/main/integration-event-bridge.ts:1775 and regression coverage in src/main/__tests__/integration-event-bridge.test.ts:2348.
  • agent-relay-code[bot] issuecomment-4653640265: same two advisory findings as above; current checkout includes the renderer mock fix at src/renderer/src/lib/ipc-mock.ts:627 and the self-bot learning guard at src/main/integration-event-bridge.ts:2767.
  • Senior reviewer finding: Slack relay echoes with bot_profile but without user_is_bot were not recognized as bot-authored, so they would not be suppressed; fixed in src/main/integration-event-bridge.ts:1812 with regression fixture coverage in src/main/__tests__/integration-event-bridge.test.ts:2362.

I did not print READY because GitHub-side required CI completion and mergeability were not available to verify from the exposed tools.

@agent-relay-code

Copy link
Copy Markdown
Contributor

ℹ️ pr-reviewer: review only — no file changes were applied to the PR (nothing to commit after review). The notes below are advisory and were not pushed.

Reviewed PR #177 against the checked-out diff and current PR state. I did not make code changes; the current checkout already contains the prior fixes. Working tree is clean.

Validation run:

  • node --experimental-strip-types --no-warnings --test --test-name-pattern "slack self-bot writeback echoes" src/main/__tests__/integration-event-bridge.test.ts
  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm test
  • npm run build:web
  • npm run build
  • npx tsc --noEmit

I had to repair local dependencies with npm install --ignore-scripts and npm rebuild --ignore-scripts; the unrelated package-lock.json change from that was restored. GitHub API shows PR head 96964b42c9f0c3850882e9a8912d732b04011e87, mergeable state clean, and both Actions checks completed successfully.

Addressed comments

  • gemini-code-assist[bot]: raised only a daily quota warning; no code issue to fix.
  • coderabbitai[bot]: review was skipped because the PR is draft; no actionable review comments or inline threads were present.
  • agent-relay-code[bot] issuecomment-4653538667: raised missing eventsSelfEchoSuppressed in the renderer IPC mock; already fixed in src/renderer/src/lib/ipc-mock.ts:627.
  • agent-relay-code[bot] issuecomment-4653596453 / issuecomment-4653624581: noted same-text Slack posts from another bot could be learned as Pear’s self bot before the real echo; already fixed by requiring the relay bot author name in src/main/integration-event-bridge.ts:1793 and applying that gate in src/main/integration-event-bridge.ts:2767, with regression coverage in src/main/__tests__/integration-event-bridge.test.ts:2348.

Not printing READY because the PR is still marked Draft / hold-for-merge.

@agent-relay-code

Copy link
Copy Markdown
Contributor

I found no additional current breakage in the PR checkout, so I made no new edits.

Local validation passed:

  • node --experimental-strip-types --no-warnings --test --test-name-pattern "slack self-bot writeback echoes" src/main/__tests__/integration-event-bridge.test.ts
  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npx tsc --noEmit
  • npm run build:web
  • npm run build
  • npm test

Addressed comments

  • gemini-code-assist[bot] issuecomment-4653463944: raised only a daily quota warning; no code issue to fix.
  • coderabbitai[bot] issuecomment-4653464524: review was skipped because the PR is draft; no actionable review comments or inline threads.
  • agent-relay-code[bot] issuecomment-4653538667: raised missing eventsSelfEchoSuppressed in the renderer IPC mock; already fixed in src/renderer/src/lib/ipc-mock.ts:627.
  • agent-relay-code[bot] issuecomment-4653596453: raised same-text Slack posts from another bot being learnable as Pear’s self bot; already fixed by the relay-bot author-name guard in src/main/integration-event-bridge.ts:1793 and src/main/integration-event-bridge.ts:2768, with regression coverage in src/main/__tests__/integration-event-bridge.test.ts:2348.
  • agent-relay-code[bot] issuecomment-4653640265: repeated the renderer mock and same-text bot mislearn findings; current checkout already has the fixes at src/renderer/src/lib/ipc-mock.ts:627 and src/main/integration-event-bridge.ts:2768.
  • agent-relay-code[bot] issuecomment-4653713206: raised Slack relay echoes with bot_profile but without user_is_bot; already fixed in src/main/integration-event-bridge.ts:1812, with fixture coverage in src/main/__tests__/integration-event-bridge.test.ts:2362.

I am not printing READY because the PR is still marked draft and its body contains an explicit no-merge hold.

agent-relay-code Bot added a commit that referenced this pull request Jun 8, 2026
@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer applied fixes — committed and pushed 7dde964 to this PR. The notes below describe what changed.

Reviewed the PR diff and fixed one demonstrated gap: Slack outbound writeback capture now works for bounded Slack command roots even when historical download is off, which is the narrow writeback mode. I also made missing local draft files a no-op during capture so cleanup/delete races do not emit noisy warnings.

Changed:

  • src/main/integration-event-bridge.ts:978
  • src/main/integration-event-bridge.ts:2712
  • src/main/__tests__/integration-event-bridge.test.ts:2006

Verified:

  • npm test -- --test-name-pattern='local fallback watchers|slack self-bot writeback echoes|confirmed Slack writeback|Slack writeback draft cleanup'
  • npm run build:web
  • npm run build

Addressed comments

  • No bot or reviewer comments were present in .workforce/context.json or the .workforce files available in this checkout.

@agent-relay-code

Copy link
Copy Markdown
Contributor

Reviewed PR #177 against .workforce/pr.diff, changed files, current checkout, and PR comments. I made no new code edits; the current checkout already contains the prior fixes for the actionable findings.

Validation:

  • Passed: node --experimental-strip-types --no-warnings --test --test-name-pattern "slack self-bot writeback echoes" src/main/__tests__/integration-event-bridge.test.ts
  • Passed: node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • Passed: npx tsc --noEmit
  • Blocked: npm run build:web initially failed because vite was missing. npm ci / npm install attempts were killed silently during extraction; I removed the partial node_modules afterward.
  • npm test could not be left as a valid signal after the failed dependency install poisoned node_modules.

Addressed comments

  • gemini-code-assist[bot] issuecomment-4653463944: raised only a daily quota warning; no code issue to fix.
  • coderabbitai[bot] issuecomment-4653464524: review skipped because PR is draft; no actionable thread.
  • agent-relay-code[bot] issuecomment-4653538667: missing eventsSelfEchoSuppressed in renderer IPC mock; already fixed in src/renderer/src/lib/ipc-mock.ts:627.
  • agent-relay-code[bot] issuecomment-4653596453: same-text Slack post from another bot could be learned as Pear’s self bot; already fixed by relay-name guard in src/main/integration-event-bridge.ts:1793 and use site at src/main/integration-event-bridge.ts:2768, with coverage in src/main/__tests__/integration-event-bridge.test.ts:2348.
  • agent-relay-code[bot] issuecomment-4653640265: repeated renderer mock and same-text bot mislearn findings; already fixed at src/renderer/src/lib/ipc-mock.ts:627 and src/main/integration-event-bridge.ts:2768.
  • agent-relay-code[bot] issuecomment-4653713206: Slack relay echoes with bot_profile but no user_is_bot were not recognized as bot-authored; already fixed in src/main/integration-event-bridge.ts:1812, with fixture coverage in src/main/__tests__/integration-event-bridge.test.ts:2362.
  • agent-relay-code[bot] issuecomment-4653798361: review-only status; no new actionable code issue.
  • agent-relay-code[bot] issuecomment-4653801302: review-only status; no new actionable code issue.

Not printing READY: the GitHub PR page still marks the PR as Draft with an explicit hold-for-merge, and local dependency installation failed before build checks could be rerun.

@khaliqgant khaliqgant force-pushed the fix/integration-event-self-echo-suppression branch from 7dde964 to e62cd0d Compare June 8, 2026 22:30
@khaliqgant khaliqgant added the no-agent-relay-review Disable agent-relay automated PR review/fixes label Jun 9, 2026
Base automatically changed from fix/integration-event-dedup-logical-identity to main June 9, 2026 05:40
@khaliqgant khaliqgant marked this pull request as ready for review June 9, 2026 05:43
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@khaliqgant khaliqgant merged commit 1a5bd5d into main Jun 9, 2026
3 checks passed
@khaliqgant khaliqgant deleted the fix/integration-event-self-echo-suppression branch June 9, 2026 05:43
kjgbot added a commit that referenced this pull request Jun 9, 2026
… telemetry (#182)

#177 added eventsSelfEchoSuppressed to IntegrationEventCounterName but the
ipc-mock telemetry totals object was not updated, breaking `tsc -p
tsconfig.web.json` on main (TS2741). Add the field (default 0) to the mock.

Co-authored-by: kjgbot <kjgbot@agentrelay.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-agent-relay-review Disable agent-relay automated PR review/fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant