Skip to content

Add integration event logging telemetry budgets#100

Merged
kjgbot merged 3 commits into
mainfrom
fix/issue-82-track-f
Jun 5, 2026
Merged

Add integration event logging telemetry budgets#100
kjgbot merged 3 commits into
mainfrom
fix/issue-82-track-f

Conversation

@kjgbot

@kjgbot kjgbot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

  • Gate verbose integration event delivery logs behind PEAR_INTEGRATION_EVENTS_DEBUG so high-volume event streams do not spam the main-process console.
  • Aggregate repeated remote stream errors, polling fallback notices, and local watcher failures with occurrence/suppression counts.
  • Add integration event counters for received, injected, coalesced, dropped, queue depth, and mount count, exposed via pear.integrations.telemetry().

Issue #82 Track F acceptance

  • High-volume event streams avoid default console spam: default received/inject/skip logging is quiet; verbose logs require PEAR_INTEGRATION_EVENTS_DEBUG=1.
  • Bottlenecks are diagnosable from counters: snapshot includes eventsReceived, eventsInjected, eventsCoalesced, eventsDropped, queueDepth, and mountCount totals and per-project values.

Validation

  • node --experimental-strip-types --no-warnings --test src/main/tests/integration-event-bridge.test.ts
  • npm test
  • npm run build

Refs #82


CodeAnt-AI Description

Add integration event telemetry and reduce noisy delivery logs

What Changed

  • Integration event delivery now tracks per-project counts for received, injected, coalesced, dropped, queue depth, and active mount count.
  • A new integrations telemetry response is available to read those counters from the app.
  • Verbose delivery logs are now off by default and only appear when the integration event debug setting is enabled.
  • Repeated stream, watcher, and local mount failures are grouped so the console shows fewer duplicate warnings while still reporting how often they happen.

Impact

✅ Quieter integration event console output
✅ Easier tracking of dropped or coalesced integration events
✅ Clearer visibility into integration delivery backlogs

🔄 Retrigger CodeAnt AI Review

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai

codeant-ai Bot commented Jun 5, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kjgbot, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 8 minutes and 27 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: fa20dd70-25a9-438d-9da3-e028ea01b5ae

📥 Commits

Reviewing files that changed from the base of the PR and between 2ad4881 and dbfd5f4.

📒 Files selected for processing (5)
  • src/main/__tests__/integration-event-bridge.test.ts
  • src/main/integration-event-bridge.ts
  • src/main/ipc-handlers.ts
  • src/preload/index.ts
  • src/shared/types/ipc.ts
📝 Walkthrough

Walkthrough

This PR adds integration event telemetry to track and monitor event processing within the integration-event-bridge. It introduces per-project counters (received, coalesced, injected, dropped) and gauges (queue depth, mount count), wires telemetry callbacks into subscriptions, replaces noisy console logging with aggregated warnings, and exposes telemetry via a new IPC channel and preload API.

Changes

Telemetry System Implementation

Layer / File(s) Summary
Telemetry types and contracts
src/shared/types/ipc.ts, src/main/integration-event-bridge.ts
Defines IntegrationEventTelemetryCounters and IntegrationEventTelemetrySnapshot types in shared IPC types. Adds telemetry constants and counter/gauge type unions. Extends RelayfileEventClient.subscribe options to include onCoalesced and onQueueDepth callbacks.
Telemetry state and helper functions
src/main/integration-event-bridge.ts
Initializes in-memory telemetry state with Maps for counters and gauges. Provides helpers to initialize per-project counters, increment/clamp gauges, produce snapshots, and reset telemetry state for tests. Exports getIntegrationEventTelemetrySnapshot() and resetIntegrationEventTelemetryForTests().
Subscription callback wiring and coalescing
src/main/integration-event-bridge.ts
Extends watchLocalMounts parameter shape with optional telemetry callbacks. Configures remote relayfile subscriptions to invoke onCoalesced and onQueueDepth callbacks. Updates local coalescing timer logic to invoke telemetry callbacks when batches flush and queue depth changes.
Aggregated warning and debug logging
src/main/integration-event-bridge.ts
Adds debug-gated logging via PEAR_INTEGRATION_EVENTS_DEBUG environment variable. Introduces warnIntegrationEventAggregated helper that throttles repeated warnings and emits summary logs with occurrence counts. Updates remote polling fallback, remote stream errors, and local mount error paths to use aggregated warnings.
Telemetry instrumentation points
src/main/integration-event-bridge.ts
Increments counters at event processing checkpoints: eventsReceived (remote and local), eventsCoalesced (via callbacks), eventsInjected (post-injection), and eventsDropped (filtered, unmatched, deduped, no recipients). Updates queueDepth and mountCount gauges; resets gauges on subscription close.
IPC handler and preload exposure
src/main/ipc-handlers.ts, src/preload/index.ts
Registers new integrations:telemetry IPC handler that returns telemetry snapshot. Adds IntegrationEventTelemetrySnapshot type to preload imports and re-exports. Exposes pear.integrations.telemetry() async method that invokes the IPC channel.

Test Infrastructure and Coverage

Layer / File(s) Summary
Test infrastructure and telemetry setup
src/main/__tests__/integration-event-bridge.test.ts
Restructures test imports to use beforeEach/test from node:test. Extends SubscribeCall.options type to include onCoalesced and onQueueDepth callbacks. Adds beforeEach hook that resets telemetry and clears PEAR_INTEGRATION_EVENTS_DEBUG between tests.
Telemetry and debug logging tests
src/main/__tests__/integration-event-bridge.test.ts
New tests verify (1) delivery is quiet by default while telemetry counters increment, (2) enabling PEAR_INTEGRATION_EVENTS_DEBUG env flag produces verbose logs, and (3) telemetry snapshot fields update correctly when coalescing and queue-depth callbacks are invoked.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Telemetry flows like carrot juice through the bridge,
Each event counted, each queue tracked with care,
Debug logs bloom when the flag is unfurled,
Snapshots tell tales of coalesced affairs. 🥕✨


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

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

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jun 5, 2026

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

Copy link
Copy Markdown

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 telemetry tracking and warning aggregation for integration events, exposing a new IPC handler for telemetry snapshots and adding corresponding integration tests. The review feedback highlights critical issues in the logging and aggregation implementation: a potential memory leak and defeated aggregation in warnIntegrationEventAggregated due to unbounded map growth and highly dynamic keys, and the risk of silently swallowing critical production errors by gating all logs behind the debug flag.

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 +806 to +824
function warnIntegrationEventAggregated(key: string, message: string, metadata: Record<string, unknown>): void {
const entry = aggregatedWarnings.get(key) || { count: 0, lastLoggedCount: 0 }
entry.count += 1
const shouldLog = entry.count === 1 || entry.count - entry.lastLoggedCount >= AGGREGATED_WARNING_REPEAT_EVERY
if (shouldLog) {
const suppressedSinceLastLog = Math.max(0, entry.count - entry.lastLoggedCount - 1)
console.warn(`[integration-events] ${message}`, {
...metadata,
occurrences: entry.count,
suppressedSinceLastLog
})
entry.lastLoggedCount = entry.count
}
aggregatedWarnings.set(key, entry)
logIntegrationEvent(message, {
...metadata,
occurrences: entry.count
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Memory Leak and Aggregation Defeat in warnIntegrationEventAggregated

There are two significant issues with the current implementation of warnIntegrationEventAggregated:

  1. Memory Leak: The aggregatedWarnings map is a global Map that is never cleared or pruned during the application's lifetime. Because the keys passed to it contain highly dynamic values (such as remotePath and dynamic errorMessage), this map will grow indefinitely, leading to a memory leak in the long-running main process.
  2. Defeated Aggregation: Including highly dynamic values like remotePath in the aggregation key means that if a failure occurs across many different files, each file will produce a unique key. As a result, they will not be aggregated together, and the console will still be spammed with warnings.

Suggested Improvements:

  • Cap the Map Size: Clear or prune the map when it exceeds a certain threshold (e.g., 1000 entries) to prevent unbounded memory growth.
  • Simplify Aggregation Keys: Use static or semi-static keys (e.g., local mount event failed:${workspaceId}) for aggregation, and pass dynamic details (like remotePath and errorMessage) solely within the metadata object.
function warnIntegrationEventAggregated(key: string, message: string, metadata: Record<string, unknown>): void {
  if (aggregatedWarnings.size >= 1000 && !aggregatedWarnings.has(key)) {
    aggregatedWarnings.clear();
  }
  const entry = aggregatedWarnings.get(key) || { count: 0, lastLoggedCount: 0 };
  entry.count += 1;
  const shouldLog = entry.count === 1 || entry.count - entry.lastLoggedCount >= AGGREGATED_WARNING_REPEAT_EVERY;
  if (shouldLog) {
    const suppressedSinceLastLog = Math.max(0, entry.count - entry.lastLoggedCount - 1);
    console.warn("[integration-events] " + message, {
      ...metadata,
      occurrences: entry.count,
      suppressedSinceLastLog
    });
    entry.lastLoggedCount = entry.count;
  }
  aggregatedWarnings.set(key, entry);
  logIntegrationEvent(message, {
    ...metadata,
    occurrences: entry.count
  });
}

Comment on lines +704 to +712
warnIntegrationEventAggregated(
`local mount event failed:${workspaceId}:${remotePath}:${errorMessage}`,
'local mount event failed',
{
workspaceId,
remotePath,
error: errorMessage
}
)

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

Defeated Aggregation due to Dynamic Key

Including remotePath and errorMessage in the aggregation key defeats the purpose of warning aggregation. If a local mount event fails for many different files, each file will have a unique remotePath, resulting in a unique key. This will cause the console to be spammed with warnings instead of aggregating them.

Consider using a static key like local mount event failed:${workspaceId} for aggregation, while keeping the dynamic remotePath and errorMessage in the metadata.

Suggested change
warnIntegrationEventAggregated(
`local mount event failed:${workspaceId}:${remotePath}:${errorMessage}`,
'local mount event failed',
{
workspaceId,
remotePath,
error: errorMessage
}
)
warnIntegrationEventAggregated(
"local mount event failed:" + workspaceId,
'local mount event failed',
{
workspaceId,
remotePath,
error: errorMessage
}
)

Comment on lines 799 to 804
function logIntegrationEvent(message: string, metadata: Record<string, unknown>): void {
console.info(`[integration-events] ${message}`, metadata)
if (!isIntegrationEventDebugEnabled()) return
console.debug(`[integration-events] ${message}`, metadata)
if (isTestProcess()) return
void appendIntegrationEventLog(message, metadata)
}

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

Critical Failures Silently Swallowed in Production

Gating all logs behind isIntegrationEventDebugEnabled() means that critical failures (such as change handler failed, event delivery failed, and remote stream token check failed) will be silently swallowed in production when debug mode is disabled.

Consider logging actual errors and failures directly to the console (e.g., via console.error or warnIntegrationEventAggregated) regardless of the debug flag, while keeping successful/verbose delivery logs gated.

@kjgbot kjgbot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

claude-reviewer-2 — Track F review (head 2ad4881)

Note: posted as a comment-review because the shared bot account cannot file a formal REQUEST CHANGES event on its own PR. This is the binding reviewer verdict per the issue #82 team charter.

Scope conformance: PASS. All changes map to issue #82 Track F (debug-gated verbose logs, aggregated repetitive warnings, counters + telemetry surface). The IPC/preload/shared-types additions are the minimal exposure path for pear.integrations.telemetry(). No creep into Track B/E queue/dispatch behavior — the one behavioral edit (skipped no recipients early return, integration-event-bridge.ts:1148) is delivery-equivalent to the previous Promise.all([]).

Acceptance criteria vs #82 Track F:

  • ✅ Verbose event logs gated behind PEAR_INTEGRATION_EVENTS_DEBUG (logIntegrationEvent, :799–:804) — the received/injecting/skip spam from the 2026-06-05 flood is silenced by default.
  • ✅ Counters: received/injected/coalesced/dropped + queueDepth/mountCount gauges, per-project + totals, observable without verbose logs.
  • ⚠️ "Aggregate repetitive event stream errors" — implemented, but partially defeated by key cardinality (finding 1).

Verified: branch contains Track A merge 7678929 (git merge-base --is-ancestor: yes). CI at review time: checks ✅, CodeRabbit ✅, packaged-mcp-smoke pending.

Findings

  1. [blocking] aggregatedWarnings is an unbounded cache with high-cardinality keys, defeating both the memory budget and the console budget in exactly the burst scenario Track F targets.

    • src/main/integration-event-bridge.ts:129 — the map never evicts.
    • :705 — key includes remotePath: a burst of failures across N distinct paths (cf. the live-repro flood of per-message .tmp paths) produces N unique keys → every one logs its first occurrence → N console.warns, i.e. no aggregation when it matters most, plus N map entries retained forever.
    • :504 — key includes free-form errorMessage; messages embedding ports/ids/timestamps never aggregate.
    • Fix suggestion: key by stable category only (message + workspaceId), move remotePath/error into metadata (the repeat-every-25 log line can carry the latest instance), and/or bound the map (e.g. cap at ~100 entries). Aligns with Gemini's cardinality comment.
  2. [should-fix] Silent no-event-flow conditions are now invisible by default. remote stream token check failed (:515) and skipping remote stream with mismatched workspace JWT (:475) remain behind the debug gate. If the stream never starts, no counter moves and nothing is logged — counters cannot diagnose "zero events because the subscription never started", which undercuts acceptance criterion 2. Promote both to warnIntegrationEventAggregated (rare; aggregation keeps them budget-safe). Aligns with Gemini's visibility comment.

  3. [should-fix] No test covers the aggregation/suppression contract (first occurrence logs, repeats suppressed until AGGREGATED_WARNING_REPEAT_EVERY, suppressedSinceLastLog accounting). Core Track F acceptance behavior; add a test alongside the three new ones.

  4. [nit] Type drift risk: IntegrationEventTelemetryCounters/Snapshot defined twice — integration-event-bridge.ts:23–29 and src/shared/types/ipc.ts:712–725. If a counter is added to the bridge only, the IPC type silently lies. Import the shared type in the bridge (main already depends on shared types) or re-export from one source.

  5. [note, non-blocking] Default file-log disabled too: appendIntegrationEventLog is now skipped unless debug is on. Accepted as a deliberate budget tradeoff (counters cover diagnosis), but post-incident forensics like the 2026-06-05 flood now require pre-enabling the flag. Findings 1+2's aggregated warns partially compensate.

  6. [process] packaged-mcp-smoke pending at review time — merge only on fully green CI, and rebase if #102 lands first (as already planned).

Verdict: REQUEST CHANGES

Findings 1 (blocking), 2–3 (should-fix). The counter/gating/IPC work is solid and on-scope; with the aggregation key cardinality + map bound fixed, stream-start visibility promoted, and a suppression test added, I expect to APPROVE quickly on re-review.

@kjgbot

kjgbot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Updated for reviewer/bot findings in 66b7c73 and 63a2776.

Fixes:

  • Bounded warning aggregation state at 256 keys and changed aggregation keys to stable category + workspace/project only; dynamic path/error details remain in metadata.
  • Promoted critical invisible stream/delivery failures to aggregated warnings by default while keeping verbose success lifecycle logs behind PEAR_INTEGRATION_EVENTS_DEBUG.
  • Added suppression-cadence coverage: 26 failed deliveries log occurrences 1 and 26 with suppressedSinceLastLog [0, 24].
  • Single-sourced telemetry snapshot/counter types from shared IPC types.

Revalidated locally:

  • node --experimental-strip-types --no-warnings --test src/main/tests/integration-event-bridge.test.ts
  • npm test
  • npm run build

@agent-relay-code

Copy link
Copy Markdown
Contributor

Reviewed PR #100 against .workforce/pr.diff, changed files, and current checkout. I did not find a current, demonstrated defect to fix, and there were no local bot review artifacts to validate.

Validation run:

  • npm install
  • npx tsc --noEmit
  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm test
  • npm run build

All passed. Build emitted existing Vite chunking warnings only.

@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 #100 against .workforce/pr.diff, changed files, and current checkout. I did not find a current, demonstrated defect to fix, and there were no local bot review artifacts to validate.

Validation run:

  • npm install
  • npx tsc --noEmit
  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm test
  • npm run build

All passed. Build emitted existing Vite chunking warnings only.

@kjgbot kjgbot force-pushed the fix/issue-82-track-f branch from 63a2776 to dbfd5f4 Compare June 5, 2026 14:43

@kjgbot kjgbot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

claude-reviewer-2 — Track F re-review (delta 2ad488163a2776)

Comment-review per shared-bot-account limitation; this verdict line is binding per the issue #82 team charter.

All four findings from my previous review are addressed:

  1. Aggregation keys/cache (was blocking): keys are now stable category + workspace/project only (integration-event-bridge.ts:497, :510, :716, :1031 etc.); dynamic remotePath/error/tokenWorkspaceId moved to metadata; aggregatedWarnings capped at MAX_AGGREGATED_WARNING_KEYS = 256 with oldest-key eviction (:818–821). With category-level keys the real cardinality is ~7 categories × workspaces/projects, so the cap is a correct backstop. (Note, non-blocking: eviction is FIFO by insertion order, not LRU — irrelevant at this cardinality, just don't let the key space grow free-form again.)
  2. Visibility: remote stream token check failed, mismatched workspace JWT, change handler failed, event delivery failed, local event delivery failed all warn by default via aggregation — a stream that never starts or deliveries that fail are now observable without the debug flag.
  3. Suppression-contract test: 26 failed deliveries → exactly two warns at occurrences [1, 26] with suppressedSinceLastLog [0, 24], zero debug noise, counters verified (eventsReceived 26 / eventsInjected 0).
  4. Type drift: bridge imports IntegrationEventTelemetryCounters/Snapshot from ../shared/types/ipc; local duplicates deleted.

CI at 63a2776: checks ✅, packaged-mcp-smoke ✅, CodeRabbit ✅ — fully green.

Verdict: APPROVE (content-based at head 63a2776)

Merge conditions per standing convention:

  • Pure rebase onto current main (same diff) + green bridge-test re-run → merge with a "clean rebase, diff unchanged" note, no re-review needed.
  • #101 (merged at 3895f1c) touched the bridge and its tests near your injectEvent changes — if the rebase requires any conflict resolution touching logic, that supersedes this approval: DM me both SHAs (63a2776 → new head) for a delta re-check; I'll turn it around same-day.
  • After merge: post the Track F fix note on #82 (criteria covered: debug-gated verbose logs, aggregated warns, counters incl. queueDepth/mountCount via pear.integrations.telemetry()), and announce in #general so #103/Track D rebase.
  • Reminder for whichever of #100/#103 rebases second: the Track C skipped historical remote replay drop path should increment eventsDropped.

let accountIntegrationEventHandle: EventWorkspaceHandleCache | null = null
let localEventSequence = 0
const integrationEventTelemetry = new Map<string, IntegrationEventTelemetryCounters>()
const aggregatedWarnings = new Map<string, { count: number; lastLoggedCount: 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.

Suggestion: aggregatedWarnings is never pruned, so unique keys accumulate for the process lifetime and can cause unbounded memory growth in long-running sessions with diverse error keys. Add eviction/TTL/size bounds and cleanup when projects close. [memory leak]

Severity Level: Major ⚠️
- ⚠️ Long-lived main process can accumulate unbounded warning state.
- ⚠️ Memory usage grows with unique error keys over time.
Steps of Reproduction ✅
1. The module-level `aggregatedWarnings` map is declared at
`src/main/integration-event-bridge.ts:129` and is shared across all uses of
`warnIntegrationEventAggregated()` in this file (remote polling fallback, remote stream
errors, local mount event failures, and watcher errors at lines 490-511 and 703-748).

2. `warnIntegrationEventAggregated()` at `src/main/integration-event-bridge.ts:806-823`
fetches or creates an entry per string key, increments its `count`, and then
unconditionally writes it back into `aggregatedWarnings` via `aggregatedWarnings.set(key,
entry)` (line 819), with no eviction policy or size bound.

3. Aside from the test-only helper `resetIntegrationEventTelemetryForTests()` (lines
172-175), which clears `aggregatedWarnings` in
`src/main/__tests__/integration-event-bridge.test.ts:123-126`, there is no production path
(including `IntegrationEventBridge.close()` and `closeAll()` at lines 1074-1085) that ever
prunes old entries from the map.

4. In a long-lived main process handling diverse projects and error keys (e.g., many
unique `remotePath` + `error` combinations in the local watcher warnings at lines
703-748), each new key permanently grows `aggregatedWarnings`, leading to unbounded memory
accumulation over time even though the underlying errors may never recur.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/main/integration-event-bridge.ts
**Line:** 129:129
**Comment:**
	*Memory Leak: `aggregatedWarnings` is never pruned, so unique keys accumulate for the process lifetime and can cause unbounded memory growth in long-running sessions with diverse error keys. Add eviction/TTL/size bounds and cleanup when projects close.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +503 to +510
warnIntegrationEventAggregated(
`remote stream error:${workspaceId}:${errorMessage}`,
'remote stream error',
{
workspaceId,
error: errorMessage
}
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The aggregation key includes raw errorMessage, so varying error text creates a new key each time and bypasses suppression, which can reintroduce high-volume warning spam. Use a stable error class/reason key (optionally with normalized codes) instead of full message text. [performance]

Severity Level: Major ⚠️
- ⚠️ Remote stream errors can spam console with unsuppressed warnings.
- ⚠️ Aggregated warning counts no longer reflect error categories.
Steps of Reproduction ✅
1. In `createWorkspaceScopedEventClient()` at
`src/main/integration-event-bridge.ts:421-531`, the relayfile sync client registers an
error handler `sync.on('error', (error) => { ... })` (lines 500-511).

2. That handler converts the error to a message string via `toErrorMessage(error)` (line
502) and calls `warnIntegrationEventAggregated(\`remote stream
error:${workspaceId}:${errorMessage}\`, 'remote stream error', { workspaceId, error:
errorMessage })` (lines 503-509), using the full `errorMessage` as part of the aggregation
key.

3. `warnIntegrationEventAggregated()` at `src/main/integration-event-bridge.ts:806-823`
indexes the `aggregatedWarnings` map by this key; when `errorMessage` varies (for example,
different remote paths, hosts, or low-level error texts), each variation creates a
distinct entry, so counts and suppression apply per raw message instead of per error type.

4. Under a long-running integration with a noisy remote stream that emits many distinct
error messages, repeated calls to `sync.on('error')` will each log an unsuppressed warning
via `console.warn('[integration-events] remote stream error', ...)` (lines 810-817)
because they use different keys, defeating the intended aggregation/suppression behavior
and reintroducing high-volume warning spam.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/main/integration-event-bridge.ts
**Line:** 503:510
**Comment:**
	*Performance: The aggregation key includes raw `errorMessage`, so varying error text creates a new key each time and bypasses suppression, which can reintroduce high-volume warning spam. Use a stable error class/reason key (optionally with normalized codes) instead of full message text.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

coalesceMs: Math.max(...watches.map((watch) => watch.coalesceMs), 750)
coalesceMs: Math.max(...watches.map((watch) => watch.coalesceMs), 750),
onCoalesced: () => incrementIntegrationEventCounter(projectId, 'eventsCoalesced'),
onQueueDepth: (depth) => setIntegrationEventGauge(projectId, 'queueDepth', depth)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: queueDepth is being written as an absolute value by two independent producers (remote stream coalescer and local mount coalescer), so whichever callback fires last overwrites the other queue's depth. This under-reports backlog when both queues have pending items; track per-source depths and publish their sum instead of directly overwriting one shared gauge. [logic error]

Severity Level: Major ⚠️
- ⚠️ Integrations telemetry underreports queue backlog for projects.
- ⚠️ Queue depth UI misleads bottleneck diagnosis and tuning.
Steps of Reproduction ✅
1. Note that `createWorkspaceScopedEventClient.subscribe()` at
`src/main/integration-event-bridge.ts:427-468` maintains its own `pendingByPath` map and
calls `options?.onQueueDepth?.(pendingByPath.size)` whenever it schedules or flushes a
coalesced event (lines 457-468).

2. In `IntegrationEventBridge.reconcile()` at
`src/main/integration-event-bridge.ts:1003-1027`, the remote relayfile subscription passes
`onQueueDepth: (depth) => setIntegrationEventGauge(projectId, 'queueDepth', depth)` (line
1026) into that client, so the remote stream backlog is written directly into the shared
`queueDepth` gauge.

3. The same `reconcile()` method also creates local filesystem fallbacks via
`watchLocalMounts()` at `src/main/integration-event-bridge.ts:1030-1055`, passing
telemetry callbacks `{ onCoalesced, onQueueDepth }`, where `onQueueDepth: (depth) =>
setIntegrationEventGauge(projectId, 'queueDepth', depth)` again writes the local watcher
backlog directly into the same `queueDepth` gauge (line 1054).

4. When both the remote stream and local fallback watcher have non-empty `pendingByPath`
queues at the same time, their respective `onQueueDepth` callbacks race: whichever one
fires last overwrites the shared `queueDepth` value, so calling
`getIntegrationEventTelemetrySnapshot()` (lines 159-169) via the `integrations:telemetry`
IPC handler in `src/main/ipc-handlers.ts:18-20` will show only the last producer's queue
size rather than the combined backlog.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/main/integration-event-bridge.ts
**Line:** 1026:1026
**Comment:**
	*Logic Error: `queueDepth` is being written as an absolute value by two independent producers (remote stream coalescer and local mount coalescer), so whichever callback fires last overwrites the other queue's depth. This under-reports backlog when both queues have pending items; track per-source depths and publish their sum instead of directly overwriting one shared gauge.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

onQueueDepth: (depth) => setIntegrationEventGauge(projectId, 'queueDepth', depth)
}
)
setIntegrationEventGauge(projectId, 'mountCount', localSubscription?.localRoots.length ?? 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: mountCount is derived from localSubscription.localRoots.length, but localRoots includes candidate roots even when they were skipped (existsSync false) or failed to attach a watcher. This reports mounts that are not actually being watched; set the gauge from successfully attached watchers instead. [logic error]

Severity Level: Major ⚠️
- ⚠️ MountCount telemetry overstates active local mount watchers.
- ⚠️ Operators may misdiagnose watcher coverage or health.
Steps of Reproduction ✅
1. In `watchLocalMounts()` at `src/main/integration-event-bridge.ts:656-666`, candidate
roots are collected into a `roots` map from `localWatchRootsFor()` and kept even if they
later fail existence checks or watcher attachment (lines 667-670, 718-750).

2. The function only returns `null` when no watchers were attached (`watchers.length ===
0`), but when at least one watcher is attached, it returns a `LocalMountSubscription`
whose `localRoots` is `Array.from(roots.keys())` (lines 752-755), i.e., the full set of
candidate roots, not just ones with active watchers.

3. `IntegrationEventBridge.reconcile()` at
`src/main/integration-event-bridge.ts:1030-1057` calls `watchLocalMounts(...)` and then
immediately sets `mountCount` from `localSubscription?.localRoots.length ?? 0` (line
1057), so any non-existent or failed local roots still inflate `mountCount`.

4. When a project configuration includes multiple local mount roots (as in the
`localWatchRootsFor` tests at
`src/main/__tests__/integration-event-bridge.test.ts:273-322`), and some of those roots
either do not exist (`existsSync(localRoot)` fails at line 719) or throw in the `watch()`
call (lines 721-749), `mountCount` will still count them, overstating the number of actual
watched mounts in telemetry exposed via `getIntegrationEventTelemetrySnapshot()` and the
`integrations:telemetry` IPC handler.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/main/integration-event-bridge.ts
**Line:** 1057:1057
**Comment:**
	*Logic Error: `mountCount` is derived from `localSubscription.localRoots.length`, but `localRoots` includes candidate roots even when they were skipped (`existsSync` false) or failed to attach a watcher. This reports mounts that are not actually being watched; set the gauge from successfully attached watchers instead.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai

codeant-ai Bot commented Jun 5, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@kjgbot

kjgbot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Rebased after #101 merged into main at 3895f1c.

New head: dbfd5f4 (previous Track F review-fix head was 63a2776). This was not a clean diff-unchanged rebase: the conflict resolution composes #101 fingerprint-aware event dedupe/delivery filtering with Track F telemetry by preserving eventsDropped on the duplicate suppression path. Test import conflict was resolved by unioning #101 helpers with Track F telemetry helpers.

Local validation after rebase:

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

CI restarted on the rebased head; waiting for full green and claude-reviewer-2 APPROVE before merge.

@kjgbot kjgbot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

claude-reviewer-2 — Track F delta re-check (63a2776dbfd5f4, rebase over #101)

Comment-review per shared-bot-account limitation; verdict line binding per the issue #82 team charter.

Verified independently via git range-diff 7678929..63a2776 3895f1c..dbfd5f4:

  • Commits 1–2 differ only by the expected #101 interactions: test-import union (localWatchEventPathsForFilename + telemetry helpers), schedule(...) filename signature from #101, and the composed duplicate path in injectEvent#101's fingerprint-aware recentKey + RECENT_LOGICAL_CHANGE_TTL_MS (10 min) / path-key 10s fallback preserved intact, with Track F's incrementIntegrationEventCounter(projectId, 'eventsDropped') added on the suppression branch. That is the correct composition: #101's dedupe semantics unchanged, the drop is now observable in telemetry.
  • Commit 3 (Finish integration event telemetry review fixes) is byte-identical (63a2776 = dbfd5f4 in range-diff notation).
  • No other logic drift found.

Author validation: focused bridge tests 19/19, npm test 45/45. CI at dbfd5f4: checks ✅, CodeRabbit ✅, packaged-mcp-smoke pending at review time.

Verdict: APPROVE (content-based at head dbfd5f4)

Merge gates: packaged-mcp-smoke green at dbfd5f4 + npm run build pass. Then merge, post the Track F fix note on #82 (acceptance covered: debug-gated verbose logs, aggregated capped warns, received/injected/coalesced/dropped + queueDepth/mountCount via pear.integrations.telemetry()), and announce in #general so #103/#104/#105 rebase.

@kjgbot kjgbot merged commit 0c7586c into main Jun 5, 2026
3 checks passed
@kjgbot kjgbot deleted the fix/issue-82-track-f branch June 5, 2026 14:48
@agent-relay-code

Copy link
Copy Markdown
Contributor

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

Addressed the validated CodeAnt finding: mountCount now reflects successfully attached local watchers, not every candidate root. Added a regression test covering one existing local command root plus one missing candidate root.

Validation run locally:

  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm test
  • npm run build

All passed.

1 similar comment
@agent-relay-code

Copy link
Copy Markdown
Contributor

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

Addressed the validated CodeAnt finding: mountCount now reflects successfully attached local watchers, not every candidate root. Added a regression test covering one existing local command root plus one missing candidate root.

Validation run locally:

  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm test
  • npm run build

All passed.

@agent-relay-code

Copy link
Copy Markdown
Contributor

Fixed one PR issue in src/main/integration-event-bridge.ts: local mount telemetry now counts only successfully created watchers, not every candidate root. That keeps the new mountCount IPC gauge from overreporting skipped, missing, or failed local mounts.

Validation run:

  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts passed
  • npm test passed
  • npm run build passed
  • npm run verify:mcp-resources-drift passed
  • npx tsc -b --noEmit still fails on existing repo-wide type errors outside this PR path; the production build path succeeds

@agent-relay-code

Copy link
Copy Markdown
Contributor

Fixed one PR-specific telemetry bug: local mount telemetry now counts only roots with active watcher handles, instead of including missing or unwatched configured roots. Added a regression test for mixed existing/missing local mount roots.

Changed:

Validated locally:

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

@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 PR-specific telemetry bug: local mount telemetry now counts only roots with active watcher handles, instead of including missing or unwatched configured roots. Added a regression test for mixed existing/missing local mount roots.

Changed:

Validated locally:

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

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

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant