Skip to content

DO-NOT-MERGE: Design #147 integration event re-drive#148

Open
kjgbot wants to merge 6 commits into
mainfrom
design/147-integration-event-redrive
Open

DO-NOT-MERGE: Design #147 integration event re-drive#148
kjgbot wants to merge 6 commits into
mainfrom
design/147-integration-event-redrive

Conversation

@kjgbot
Copy link
Copy Markdown
Contributor

@kjgbot kjgbot commented Jun 7, 2026

DO-NOT-MERGE: draft implementation stacked on #145. No merge until #145 lands and project-lead explicitly clears #147.

Summary:

  • Adds additive BrokerManager.sendMessageAndWaitForInjected(...), which resolves only on delivery_injected for the matching event_id and target.
  • Keeps sendMessageAndWaitForDelivery(...) behavior intact; delivery_ack / delivery_verified remain success only for that existing helper, not for Integration-event steer loss: optimistic dedup claim suppresses re-drive of lost steers (relay#1056 §5) #147 commit.
  • Changes integration-event dedupe to use provisional claims for direct-agent recipients.
  • Commits a shared provisional claim only after all tracked direct-agent recipients confirm injected delivery; releases the whole claim on any failure/timeout so a later Relayfile redelivery can re-drive the event.
  • Documents the accepted multi-recipient residual: whole-claim release may duplicate already-confirmed recipients on replay, which is the deliberate duplicate-over-drop bias for this pass.
  • Preserves no-head-of-line blocking: the project pacer starts injected-confirmation tasks and does not await confirmation completion.
  • Adds the mandatory channel guard: channel/unresolved targets never commit on targets=[]; they keep delivery flowing and log a residual warning.
  • Updates the design note with final coverage/timing rulings and the channel/mixed-target residual.

Verification:

  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts (61/61)
  • npx --yes vitest run src/main/broker.test.ts (36/36)
  • npm run build
  • git diff --check

Cross-links: #145, #147, relay#1058, relay#1056 §5.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements a provisional-to-committed dedupe state machine backed by explicit broker delivery-injection confirmations. It introduces BrokerManager.sendMessageAndWaitForInjected to wait for per-target injection events, refactors the integration event bridge to defer dedupe claims until confirmation, and updates Slack logical deduplication to track content-aware state with blind/committed transitions.

Changes

Integration Event Delivery Confirmation & Deduplication Redesign

Layer / File(s) Summary
Specification: Delivery Redrive & Confirmation Contract
docs/specs/2026-06-07-integration-event-redrive.md
Documents the shift from send acceptance to delivery_injected confirmation as the dedupe commit trigger. Defines provisional vs committed states, Slack content-hash behavior, channel-target guards, telemetry requirements, and test/rollout scope.
Broker: Injection Confirmation Method & Event Filtering
src/main/broker.ts, src/main/broker.test.ts
isDeliveryEventForMessage accepts configurable allowed event kinds. New sendMessageAndWaitForInjected waits for per-target delivery_injected events with timeout, failure handling, and replay support. Tests cover success/timeout/failure paths, multi-target coordination, empty-target fast-path, and event replay.
Bridge: State Types, Constants & Pacer Refactor
src/main/integration-event-bridge.ts
Adds DELIVERY_INJECTED_CONFIRMATION_TIMEOUT_MS constant; expands RecentInjectionState and SlackLogicalInjectionState to track provisional/committed blind/content-hash status; extends BrokerEventBridge with optional sendMessageAndWaitForInjected contract; refactors ProjectBrokerSendPacer to support per-item send callbacks and promise-based enqueue.
Injection Event: Confirmation-Driven Dedupe Lifecycle
src/main/integration-event-bridge.ts
injectEvent now gates dedupe commitment on broker confirmation. Sets up deliveryClaim for concrete-target sends, collects injectedConfirmations from broker, and commits or releases dedupe keys based on confirmation outcome with aggregated warning logging on failure. Channel and untracked targets bypass confirmation gating.
API Surface: sendBrokerMessage & Slack State Machine
src/main/integration-event-bridge.ts
sendBrokerMessage signature returns { injectedConfirmation? } and accepts waitForInjected option. claimRecentInjection tracks provisional state. claimSlackLogicalInjection rewritten to handle blind vs content-hash aliases with provisional→committed transitions; commitDedupeKey and releaseDedupeKey updated to move state across provisional/committed sets and clean up Slack logical entries when fully deduplicated.
Test: Harness Extensions & Integration Coverage
src/main/__tests__/integration-event-bridge.test.ts
makeHarness extended with waitForInjectedNeverSettles and failInjected controls to simulate broker behavior. New Slack alias/edit test verifies re-injection after content change post–blind claim. Replay tests confirm confirmation-failure path and dedupe-key release. Fanout test refactored to validate stable send order. Telemetry and warning assertions updated for injected-confirmation terminology.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • AgentWorkforce/pear#147 — Implements the provisional→committed dedupe model with broker delivery-injected confirmation, directly addressing the integration-event-bridge and broker delivery confirmation changes described in that issue.

Poem

🐰 Hops through dedupe state with glee,
Provisional claims wait patiently,
Broker confirms, and keys commit free,
No dupes slip through—our devs agree!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly indicates this is a draft PR (DO-NOT-MERGE) implementing Design #147 for integration event re-drive, which directly matches the main objective of the changeset.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering the new sendMessageAndWaitForInjected method, provisional dedupe claims, channel guards, and verification details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 design/147-integration-event-redrive
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch design/147-integration-event-redrive

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.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a design specification for implementing integration event re-drives after lost steers, transitioning from optimistic one-shot deduplication claims to provisional claims that are only committed upon delivery confirmation. The feedback on this design highlights three key areas for improvement: first, checking broker protocol versions or capability flags to prevent a 15-second timeout latency penalty on older brokers that do not support the new delivery_injected event; second, clarifying the lifecycle transition of blindClaim to ensure genuine edits are not incorrectly suppressed; and third, introducing an AbortSignal and tracking background tasks to prevent memory leaks and errors when a project is closed while tasks are still in-flight.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +101 to +102
- Unsupported brokers may return no `eventId` or no targets; the bridge treats
those as accepted-only and commits immediately, preserving current behavior.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

During rolling upgrades or when interacting with older broker versions that return an eventId but do not yet support or emit the delivery_injected event, the helper will always wait for the full 15-second timeout before failing. This would lead to a 15-second latency penalty and unnecessary duplicate re-drives for every message during the transition period.\n\nTo prevent this, the design should specify checking the broker's protocol version or capability flags (available via the session metadata) to explicitly detect if the broker supports delivery_injected, rather than relying solely on the presence of eventId or targets.

Comment on lines +151 to +152
- A blind claim can still learn the first later content hash as in #145, but that
hash stays provisional until delivery is confirmed.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To ensure genuine edits with different content hashes are not incorrectly suppressed by an active blindClaim once its content hash is resolved, the design should clarify the transition lifecycle: when the content hash is learned, the blindClaim (which is a DeliveryClaim) should be moved/promoted into the contentHashes map under that resolved hash, and the blindClaim field should be cleared.

Comment on lines +110 to +113
reintroduce the old head-of-line block. The bridge must start the helper as a
background confirmation task, attach `.then(commit).catch(release)`, and return
from the pacer callback immediately after starting the send attempt. The pacer
rate-limits starts, not confirmation completion.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since these confirmation tasks run asynchronously in the background and can take up to 15 seconds to resolve or timeout, there is a risk of memory leaks, log spam, or null-pointer/property-access errors if the project is closed or reconciled (via close(projectId)) while tasks are still in-flight.\n\nTo ensure clean teardown, the design should:\n1. Support an AbortSignal in the options of sendMessageAndWaitForInjected.\n2. Track active background confirmation tasks per project.\n3. Abort all pending tasks when close(projectId) is called.

@kjgbot kjgbot force-pushed the design/147-integration-event-redrive branch 2 times, most recently from b09e585 to f737f63 Compare June 7, 2026 16:10
@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.

Fixed one issue in docs/specs/2026-06-07-integration-event-redrive.md: the unsupported-broker guidance now matches the current sendMessageAndWaitForDelivery behavior. Direct agent sends without reported targets fall back to [input.to]; accepted-only commit is limited to no usable event_id or channel/project fanout with no target list.

Local validation passed:

  • npm test
  • npx vitest run src/main/broker.test.ts
  • npm run verify:mcp-resources-drift
  • npm run build

I did not verify GitHub-side check status, mergeability, or macOS packaging smoke from this sandbox, so I’m not marking this READY.

@kjgbot kjgbot force-pushed the design/147-integration-event-redrive branch from f737f63 to da006c9 Compare June 7, 2026 16:11
@agent-relay-code
Copy link
Copy Markdown
Contributor

Reviewed and patched PR #148.

Changed docs/specs/2026-06-07-integration-event-redrive.md to address the validated bot feedback:

  • Added AbortSignal support to sendMessageAndWaitForInjected() and required project-scoped cancellation on close(projectId).
  • Clarified unsupported broker handling: direct sends without returned targets fall back to [input.to]; only missing usable event_id or unresolved channel/project fanout can be accepted-only.
  • Added capability/protocol detection guidance so older brokers do not burn the full timeout waiting for unsupported delivery_injected.
  • Clarified blindClaim promotion into contentHashes when the first content hash is learned.
  • Added a close/abort regression test requirement.

Validation run:

  • npm test passed: 89 tests.
  • npx vitest run src/main/broker.test.ts passed: 36 tests.
  • npm run verify:mcp-resources-drift passed.
  • npm run build passed after installing dependencies.

I’m not printing READY because I cannot verify GitHub-side required check completion or mergeability from the available tools here.

@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.

Updated docs/specs/2026-06-07-integration-event-redrive.md to address the validated review findings:

  • Clarified unsupported broker handling so targets=[] only commits for direct sends that can fall back to [input.to], not channel fanout.
  • Added explicit broker/session capability detection for delivery_injected to avoid timeout penalties during rolling upgrades.
  • Added AbortSignal support and per-project task cleanup requirements for background confirmation waits.
  • Clarified blindClaim promotion into contentHashes and added test coverage bullets.
  • Fixed the broker helper test-plan wording so it waits after broker acceptance until injection/failure/timeout.

Validation passed locally:

  • npm test
  • npx --yes vitest run src/main/broker.test.ts
  • npm run verify:mcp-resources-drift
  • npm run build

I did not verify GitHub-side check status or mergeability from here, so I’m not marking this READY.

@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 #148 against the current checkout and the public PR page: #148

No code/doc edits were needed. The PR currently adds only docs/specs/2026-06-07-integration-event-redrive.md. I validated the prior Gemini findings against the current file; the unsupported-broker handling, blindClaim promotion, capability detection, and abort/cleanup requirements are already addressed. The GitHub hidden/bidi text warning did not reproduce locally: the added doc has no non-ASCII or unsafe control characters.

Local validation passed:

  • npm test passed: 89 tests
  • npx vitest run src/main/broker.test.ts passed: 36 tests
  • npm run verify:mcp-resources-drift passed
  • npm run build passed after installing dependencies

I’m not printing READY: the PR is still marked Draft / DO-NOT-MERGE on GitHub, and I can’t assert all required GitHub-side checks and mergeability are complete from here.

Base automatically changed from split/132-slack-integration-event-context-retry to main June 7, 2026 17:23
@khaliqgant khaliqgant marked this pull request as ready for review June 7, 2026 17:23
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/specs/2026-06-07-integration-event-redrive.md`:
- Line 5: The README contains line-leading issue references like "`#145`" and
"`#147`" that are being parsed as ATX headings; update the sentence to escape the
leading hashes (e.g., "\`#145`", "\`#147`") or rephrase the sentence so the issue
numbers are not at the start of the line; edit the occurrence of "`#145` has
landed and project-lead explicitly clears `#147`. The broker" (and the similar
occurrence at line 28) to use escaped hashes or alternate wording to satisfy
markdownlint MD018.

In `@src/main/broker.ts`:
- Around line 3130-3139: The timeout for waitForInjection currently starts
immediately and can reject before callers attach awaits, causing unhandled
rejections; change the logic so the timer (timer/setTimeout) is created only
after send acceptance (i.e., after sendMessage resolves and eventId/targets are
known), while still keeping resolveWait/rejectWait and buffering
pendingTargets/events produced before acceptance for later replay; update code
around
waitForInjection/resolveWait/rejectWait/settled/pendingTargets/eventId/input.to/timeoutMs
to initialize the timeout post-acceptance and ensure any buffered items are
replayed into the same promise flow so late send latencies won't trigger an
unobserved rejection.
- Around line 3112-3116: sendMessageAndWaitForInjected currently only accepts {
timeoutMs } and only short-circuits on unsupported_operation/empty targets, so
it doesn't support AbortSignal-based cancellation or metadata-based
known-unsupported short-circuits; update the function signature to accept an
AbortSignal (e.g., options: { timeoutMs?: number; signal?: AbortSignal }) and
inside sendMessageAndWaitForInjected (and the similar logic at lines ~3189-3192)
wire the signal into whatever wait/promise logic you use (clear timers/listeners
and reject/return early when aborted), and add a pre-wait check of the delivery
metadata/targets for the documented unsupported flag (per
docs/specs/2026-06-07-integration-event-redrive.md) so known-unsupported
sessions immediately return the appropriate DeliveryConfirmationResult instead
of waiting the full timeout. Ensure the returned type and behavior remain
DeliveryConfirmationResult and that both timeoutMs and signal short-circuit
cleanup are handled consistently.

In `@src/main/integration-event-bridge.ts`:
- Around line 2405-2421: This is intentional: when usesConcreteAgentTargets is
true but bridge.sendMessageAndWaitForInjected is missing, keep the hard-fail
(throw new Error('Broker delivery_injected confirmation is unavailable')) and do
not convert it to a warning; add a short clarifying comment above the
usesConcreteAgentTargets/canTrackInjectedDelivery check referencing the
delivery_injected confirmation contract and tests/mocks that expect a hard
failure, and ensure the thrown error message remains exactly 'Broker
delivery_injected confirmation is unavailable' so existing tests/specs continue
to pass.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6890a917-3a19-4932-a49e-9ef6333864af

📥 Commits

Reviewing files that changed from the base of the PR and between fe72ac1 and d863b47.

📒 Files selected for processing (5)
  • docs/specs/2026-06-07-integration-event-redrive.md
  • src/main/__tests__/integration-event-bridge.test.ts
  • src/main/broker.test.ts
  • src/main/broker.ts
  • src/main/integration-event-bridge.ts


Issue: #147. Base: #145 (`split/132-slack-integration-event-context-retry`).
Status: IMPLEMENTATION APPROVED FOR DIRECT-AGENT PATH ONLY. Do not merge until
#145 has landed and project-lead explicitly clears #147. The broker
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Escape line-leading issue references to avoid accidental headings.

#145 at line start is parsed as an ATX heading marker in Markdown, which breaks section structure. Escape the hash (or rephrase the sentence) for those references.

✏️ Suggested doc fix
-#145 has landed and project-lead explicitly clears `#147`. The broker
+\`#145` has landed and project-lead explicitly clears `#147`. The broker
...
-#145 fixes the separate "content unavailable" path, but it cannot fix a lost
+\`#145` fixes the separate "content unavailable" path, but it cannot fix a lost

As reported by markdownlint MD018 for these exact lines.

Also applies to: 28-28

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 5-5: No space after hash on atx style heading

(MD018, no-missing-space-atx)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/specs/2026-06-07-integration-event-redrive.md` at line 5, The README
contains line-leading issue references like "`#145`" and "`#147`" that are being
parsed as ATX headings; update the sentence to escape the leading hashes (e.g.,
"\`#145`", "\`#147`") or rephrase the sentence so the issue numbers are not at the
start of the line; edit the occurrence of "`#145` has landed and project-lead
explicitly clears `#147`. The broker" (and the similar occurrence at line 28) to
use escaped hashes or alternate wording to satisfy markdownlint MD018.

Source: Linters/SAST tools

Comment thread src/main/broker.ts
Comment on lines +3112 to +3116
async sendMessageAndWaitForInjected(
projectId: string | undefined,
input: SendMessageInput,
options: { timeoutMs?: number } = {}
): Promise<DeliveryConfirmationResult> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

sendMessageAndWaitForInjected is missing cancellation and known-unsupported short-circuit behavior.

The new helper only supports { timeoutMs } and only short-circuits on unsupported_operation/empty targets. That means project-close flows cannot abort in-flight waits, and known unsupported sessions can still burn full timeouts.

Based on the delivery contract documented in docs/specs/2026-06-07-integration-event-redrive.md (AbortSignal support and metadata-based unsupported detection).

Also applies to: 3189-3192

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/broker.ts` around lines 3112 - 3116, sendMessageAndWaitForInjected
currently only accepts { timeoutMs } and only short-circuits on
unsupported_operation/empty targets, so it doesn't support AbortSignal-based
cancellation or metadata-based known-unsupported short-circuits; update the
function signature to accept an AbortSignal (e.g., options: { timeoutMs?:
number; signal?: AbortSignal }) and inside sendMessageAndWaitForInjected (and
the similar logic at lines ~3189-3192) wire the signal into whatever
wait/promise logic you use (clear timers/listeners and reject/return early when
aborted), and add a pre-wait check of the delivery metadata/targets for the
documented unsupported flag (per
docs/specs/2026-06-07-integration-event-redrive.md) so known-unsupported
sessions immediately return the appropriate DeliveryConfirmationResult instead
of waiting the full timeout. Ensure the returned type and behavior remain
DeliveryConfirmationResult and that both timeoutMs and signal short-circuit
cleanup are handled consistently.

Comment thread src/main/broker.ts
Comment on lines +3130 to +3139
const waitForInjection = new Promise<void>((resolve, reject) => {
resolveWait = resolve
rejectWait = reject
timer = setTimeout(() => {
if (settled) return
settled = true
const pending = Array.from(pendingTargets)
const targetSummary = pending.length > 0 ? ` (${pending.join(', ')})` : ''
reject(new Error(`Timed out waiting for delivery injection for ${eventId || input.to}${targetSummary}`))
}, timeoutMs)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Timeout can reject before it is awaited, risking unhandled rejection on slow sends.

waitForInjection starts its timeout before sendMessage resolves. If send latency exceeds timeoutMs, the promise rejects before any await/catch is attached in the main flow.

Consider starting the timeout window after send acceptance (once event_id/targets are known) while still buffering pre-resolution events for replay.

Also applies to: 3178-3198

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/broker.ts` around lines 3130 - 3139, The timeout for
waitForInjection currently starts immediately and can reject before callers
attach awaits, causing unhandled rejections; change the logic so the timer
(timer/setTimeout) is created only after send acceptance (i.e., after
sendMessage resolves and eventId/targets are known), while still keeping
resolveWait/rejectWait and buffering pendingTargets/events produced before
acceptance for later replay; update code around
waitForInjection/resolveWait/rejectWait/settled/pendingTargets/eventId/input.to/timeoutMs
to initialize the timeout post-acceptance and ensure any buffered items are
replayed into the same promise flow so late send latencies won't trigger an
unobserved rejection.

Comment on lines +2405 to +2421
const usesConcreteAgentTargets = uniqueRecipients.every((recipient) => !recipient.startsWith('#'))
const canTrackInjectedDelivery = usesConcreteAgentTargets && typeof bridge.sendMessageAndWaitForInjected === 'function'
const shouldTrackDedupe = canTrackInjectedDelivery
if (!usesConcreteAgentTargets) {
warnIntegrationEventAggregated(
`delivery injected tracking skipped for channel targets:${projectId}`,
'delivery injected tracking skipped for channel targets',
{
projectId,
eventId: event.id,
path: event.resource.path,
recipients: uniqueRecipients
}
)
} else if (!canTrackInjectedDelivery) {
throw new Error('Broker delivery_injected confirmation is unavailable')
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there are other callers of the bridge that might not provide sendMessageAndWaitForInjected
rg -nP 'IntegrationEventBridge|broker:\s*\{' --type=ts -g '!*.test.ts' -g '!*.spec.ts' -C3

Repository: AgentWorkforce/pear

Length of output: 3214


🏁 Script executed:

rg -n --hidden --glob '!**/node_modules/**' 'sendMessageAndWaitForInjected' -S .

Repository: AgentWorkforce/pear

Length of output: 1779


🏁 Script executed:

rg -n 'BrokerEventBridge' src -S

Repository: AgentWorkforce/pear

Length of output: 806


🏁 Script executed:

sed -n '2350,2580p' src/main/integration-event-bridge.ts

Repository: AgentWorkforce/pear

Length of output: 8844


🏁 Script executed:

rg -n 'const bridge|let bridge|bridge\s*=' src/main/integration-event-bridge.ts

Repository: AgentWorkforce/pear

Length of output: 107


🏁 Script executed:

rg -n --hidden --glob '!**/node_modules/**' 'sendMessageAndWaitForInjected' -S .

Repository: AgentWorkforce/pear

Length of output: 1779


🏁 Script executed:

rg -n 'BrokerEventBridge' src -S

Repository: AgentWorkforce/pear

Length of output: 806


🏁 Script executed:

sed -n '2350,2580p' src/main/integration-event-bridge.ts

Repository: AgentWorkforce/pear

Length of output: 8844


🏁 Script executed:

rg -n 'const bridge|let bridge|bridge\s*=' src/main/integration-event-bridge.ts

Repository: AgentWorkforce/pear

Length of output: 107


🏁 Script executed:

rg -n 'start\(' src/main/integration-event-bridge.ts | head -n 50

Repository: AgentWorkforce/pear

Length of output: 90


Confirm hard-fail contract for missing sendMessageAndWaitForInjected
The integration-event bridge intentionally throws (Broker delivery_injected confirmation is unavailable) whenever all recipients are concrete agents but the broker bridge lacks sendMessageAndWaitForInjected—this is a deliberate contract, not an accidental rollout regression (the mock/tests/specs treat the missing method as a hard failure).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/integration-event-bridge.ts` around lines 2405 - 2421, This is
intentional: when usesConcreteAgentTargets is true but
bridge.sendMessageAndWaitForInjected is missing, keep the hard-fail (throw new
Error('Broker delivery_injected confirmation is unavailable')) and do not
convert it to a warning; add a short clarifying comment above the
usesConcreteAgentTargets/canTrackInjectedDelivery check referencing the
delivery_injected confirmation contract and tests/mocks that expect a hard
failure, and ensure the thrown error message remains exactly 'Broker
delivery_injected confirmation is unavailable' so existing tests/specs continue
to pass.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/main/broker.ts">

<violation number="1" location="src/main/broker.ts:3115">
P2: `sendMessageAndWaitForInjected` does not accept an `AbortSignal`, but the design spec in this same PR documents `signal?: AbortSignal` as required for project-close cleanup. Without it, `close(projectId)` cannot abort in-flight injection waits, leaving background tasks that burn the full timeout and may attempt to commit/release claims on an already-disposed project.</violation>

<violation number="2" location="src/main/broker.ts:3130">
P1: The `waitForInjection` promise starts its timeout immediately upon construction, but isn't awaited until after `sendMessage` resolves. If `sendMessage` latency exceeds `timeoutMs`, the promise rejects while no `.catch()` or `await` is attached, triggering an `unhandledRejection` event in Node.js. Start the timeout after `sendMessage` returns (once `event_id`/targets are known), or attach a no-op `.catch()` to suppress the intermediate unhandled rejection while still replaying buffered events.</violation>
</file>

<file name="src/main/integration-event-bridge.ts">

<violation number="1" location="src/main/integration-event-bridge.ts:2659">
P1: Injected confirmation promises are created without an immediate rejection handler, which can produce unhandled promise rejections before the later aggregation catch is attached.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/main/broker.ts
let resolveWait: (() => void) | undefined
let rejectWait: ((error: Error) => void) | undefined

const waitForInjection = new Promise<void>((resolve, reject) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: The waitForInjection promise starts its timeout immediately upon construction, but isn't awaited until after sendMessage resolves. If sendMessage latency exceeds timeoutMs, the promise rejects while no .catch() or await is attached, triggering an unhandledRejection event in Node.js. Start the timeout after sendMessage returns (once event_id/targets are known), or attach a no-op .catch() to suppress the intermediate unhandled rejection while still replaying buffered events.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/main/broker.ts, line 3130:

<comment>The `waitForInjection` promise starts its timeout immediately upon construction, but isn't awaited until after `sendMessage` resolves. If `sendMessage` latency exceeds `timeoutMs`, the promise rejects while no `.catch()` or `await` is attached, triggering an `unhandledRejection` event in Node.js. Start the timeout after `sendMessage` returns (once `event_id`/targets are known), or attach a no-op `.catch()` to suppress the intermediate unhandled rejection while still replaying buffered events.</comment>

<file context>
@@ -3106,6 +3109,99 @@ export class BrokerManager {
+    let resolveWait: (() => void) | undefined
+    let rejectWait: ((error: Error) => void) | undefined
+
+    const waitForInjection = new Promise<void>((resolve, reject) => {
+      resolveWait = resolve
+      rejectWait = reject
</file context>

Comment on lines +2659 to +2664
injectedConfirmation = bridge.sendMessageAndWaitForInjected!(
projectId,
message,
{ timeoutMs: DELIVERY_INJECTED_CONFIRMATION_TIMEOUT_MS }
)
return Promise.resolve()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Injected confirmation promises are created without an immediate rejection handler, which can produce unhandled promise rejections before the later aggregation catch is attached.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/main/integration-event-bridge.ts, line 2659:

<comment>Injected confirmation promises are created without an immediate rejection handler, which can produce unhandled promise rejections before the later aggregation catch is attached.</comment>

<file context>
@@ -2553,24 +2645,43 @@ export class IntegrationEventBridge {
+
+    let injectedConfirmation: Promise<unknown> | undefined
+    await pacer.enqueue(input, (message) => {
+      injectedConfirmation = bridge.sendMessageAndWaitForInjected!(
+        projectId,
+        message,
</file context>
Suggested change
injectedConfirmation = bridge.sendMessageAndWaitForInjected!(
projectId,
message,
{ timeoutMs: DELIVERY_INJECTED_CONFIRMATION_TIMEOUT_MS }
)
return Promise.resolve()
const confirmation = bridge.sendMessageAndWaitForInjected!(
projectId,
message,
{ timeoutMs: DELIVERY_INJECTED_CONFIRMATION_TIMEOUT_MS }
)
injectedConfirmation = confirmation.catch((error) => {
throw error
})
return Promise.resolve()

Comment thread src/main/broker.ts
async sendMessageAndWaitForInjected(
projectId: string | undefined,
input: SendMessageInput,
options: { timeoutMs?: number } = {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: sendMessageAndWaitForInjected does not accept an AbortSignal, but the design spec in this same PR documents signal?: AbortSignal as required for project-close cleanup. Without it, close(projectId) cannot abort in-flight injection waits, leaving background tasks that burn the full timeout and may attempt to commit/release claims on an already-disposed project.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/main/broker.ts, line 3115:

<comment>`sendMessageAndWaitForInjected` does not accept an `AbortSignal`, but the design spec in this same PR documents `signal?: AbortSignal` as required for project-close cleanup. Without it, `close(projectId)` cannot abort in-flight injection waits, leaving background tasks that burn the full timeout and may attempt to commit/release claims on an already-disposed project.</comment>

<file context>
@@ -3106,6 +3109,99 @@ export class BrokerManager {
+  async sendMessageAndWaitForInjected(
+    projectId: string | undefined,
+    input: SendMessageInput,
+    options: { timeoutMs?: number } = {}
+  ): Promise<DeliveryConfirmationResult> {
+    const session = input.to.startsWith('#')
</file context>

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant