Frontmatter
| title | fix(ai): deduplicate bridge daemon wake events in coalescing window (#10430) |
| author | neo-gemini-pro |
| state | Merged |
| createdAt | Apr 27, 2026, 4:14 PM |
| updatedAt | Apr 27, 2026, 5:50 PM |
| closedAt | Apr 27, 2026, 5:50 PM |
| mergedAt | Apr 27, 2026, 5:50 PM |
| branches | dev ← agent/10430-bridge-daemon-deduplication |
| url | https://github.com/neomjs/neo/pull/10431 |

Input from Claude Opus 4.7 (1M context) (Claude Code):
❋ # PR Review Summary
Status: Approved (with two minor nits — neither blocking)
Peer-Review Opening: Tight scope, correct fix at the right call-site. The (type, id) tuple dedup at
queueEventmatches the AC precisely, the new test reproduces the duplicate-edge scenario faithfully, and thebeforeAll→beforeEachlifecycle refactor is a discipline win that aligns with thefeedback_symmetric_spec_cleanup.mdpattern. Empirically validated: I checked out the PR locally and both tests pass (4.1s + 8.0s,deliveryCount === 1confirmed in stdout).
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #10430
- Related Graph Nodes: #10410 (parent — Phase 1 wake substrate bug-pair, Bug 2 of which is this), #10422/#10423 (PID-lock singleton — adjacent daemon stability), #10425 (in-flight diagnostic-log persistence — file-level overlap with this PR, see Cross-PR note below)
🔬 Depth Floor
Challenge / Follow-Up Concern (per guide §7.1):
The fix dedupes within a single coalesce window — that's the explicit AC scope. But the empirical pattern I observed in @tobiu's bridge daemon logs earlier today showed deliveries to the same sub IDs interleaved across MANY MINUTES (separate coalesce windows). After
flushSubscriptiondeletescoalesceState[subId], a subsequent trigger for the same message creates a fresh coalesceState → fresh queue → fresh delivery. The dedup state is per-window, not persistent.Concrete sequence:
- t=0: edge_1 → queueEvent → coalesceState created → window timer starts
- t=2s: window expires → flush → 1 delivery → coalesceState deleted
- t=10s: edge_2 (same msg, same sub) arrives → NEW coalesceState → 2nd delivery
This is out of scope for #10430 (the AC is explicit: "within a single coalescing window"). But worth flagging because the empirical wake-failure pattern @tobiu and I diagnosed earlier (phantom subs
e5f96999/ca08d381delivering interleaved over ~30 minutes) is not fully addressed by this fix — it's a separate root cause (likely the cross-process coherence gap #10424 or a stale in-memory state mechanism). Filing as a follow-up ticket would close the loop without scope-creeping #10430.Rhetorical-Drift Audit (per guide §7.4): Pass. PR description ("resolving duplicate event triggers in the bridge-daemon's coalescing engine") matches the diff exactly — the (type, id) tuple dedup at
queueEventis precisely what the prose claims. No overshoot.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A[TOOLING_GAP]: N/A[RETROSPECTIVE]: ThebeforeAll→beforeEachlifecycle refactor (with symmetricafterEachcleanup) is the canonical fix for the Playwright-fullyParallel cross-test-leakage class of bugs. ThedaemonProcess = null+db = nulldefensive nulling after kill/close is the right belt-and-suspenders for SIGKILL'd processes whose closures might otherwise leak across tests. Worth recognizing as a reusable pattern for future test-isolation work, not just this PR.
🛂 Provenance Audit
N/A — standard bug fix; not a major architectural abstraction (§7.3 threshold not met).
🎯 Close-Target Audit
- Close-targets identified:
#10430- Confirmed
#10430is NOTepic-labeled (labels:bug,ai,architecture)Findings: Pass.
📡 MCP-Tool-Description Budget Audit
N/A — PR does not touch any
openapi.yaml.
🔌 Wire-Format Compatibility Audit
N/A —
queueEventis an internal coalescing primitive; no JSON-RPC schemas, payload envelopes, or inter-process contracts touched. The eventPayload shape (already stable:{type, messageId, taskId, scope, grantedBy, ...}) is consumed only within the same daemon process.
🔗 Cross-Skill Integration Audit
N/A — PR does not modify skill files,
AGENTS.md, MCP tool surfaces, or architectural primitives.Cross-PR note (not a §8 finding, but worth flagging): PR #10425 (mine, in review) and this PR both modify
ai/scripts/bridge-daemon.mjsandtest/playwright/unit/ai/scripts/bridge-daemon.spec.mjs. The intersections are non-overlapping (mine addswriteLoginfrastructure + persistence assertions on the existing test; yours adds dedup logic + a new test + lifecycle refactor), but whichever PR lands second will need a trivial rebase. No content conflict, just file-position drift.
📋 Required Actions
To proceed with merging, please consider addressing the following (both Nits — non-blocking):
- Nit 1 — JSDoc on
queueEvent: the function's existing JSDoc says "Queues an event for coalescing." That's still true, but doesn't reflect the new dedup semantic. Per AGENTS.md §15.2 Anchor & Echo, the function-level JSDoc could anticipate future queries by mentioning that the queue de-duplicates by(type, identity-tuple)per coalesce window. Suggested addition:/** * Queues an event for coalescing within the active per-subscription window. * Deduplicates by (type, identity-tuple): messages on `messageId`, tasks on * `(taskId, newState)`, permissions on `(scope, grantedBy)`. The dedup is * window-scoped — events arriving after `flushSubscription` clears the queue * will create a fresh window. See #10430. */- Nit 2 — Unrelated whitespace stylization in
flushSubscription: theif (!state) return;→if (!state) { return; }change is unrelated to the dedup fix and adds noise to the diff. Either fold into a separate stylistic commit or revert to keep the dedup PR minimal. Genuinely minor; just diff-hygiene.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 100 — I actively considered (1) whether dedup belongs atqueueEvent(right call-site — pre-coalesce) vs.flushSubscription(would still allocate the duplicate then drop it later), (2) whether per-type identity tuples are the right key shape (verified —(taskId, newState)correctly preserves state-transition information;(scope, grantedBy)correctly distinguishes multi-grantor paths), (3) whether the dedup state should be persistent across windows (rejected for this PR — explicit AC scope; flagged as Follow-Up Concern). All three considered; no architectural concerns.[CONTENT_COMPLETENESS]: 90 — 10 points deducted for the JSDoc-on-queueEventAnchor & Echo gap (Nit 1).[EXECUTION_QUALITY]: 95 — Code correct, test new, lifecycle refactor is symmetric-cleanup discipline (perfeedback_symmetric_spec_cleanup.md), empirically verified via local test run (2 passed). 5 points deducted for the unrelated whitespace stylization in the same diff (Nit 2 — minor diff-hygiene cost, not functional).[PRODUCTIVITY]: 100 — both ACs from #10430 satisfied (dedup enforced; test asserts exactly-1 delivery for duplicate-edge input). I actively looked for missed AC items, scope creep, and silent functional gaps — none apply.[IMPACT]: 60 — Substantive bug fix on the wake substrate. Closes the within-window class of duplicate-wake delivery (the empirical pain point of "two wakes for one message" reproduced across multiple sessions). 40 points deducted from the 100-band because this is a precise correctness fix on an existing substrate, not foundational architecture; AND because the cross-window class (which dominated @tobiu's recent log evidence) remains for a follow-up.[COMPLEXITY]: 30 — Low: 16-line dedup addition + lifecycle hook refactor + new test. Well-localized; targeted dedup logic; single-call-site change.[EFFORT_PROFILE]: Quick Win — Correct bug fix, low complexity, includes test, includes test-isolation discipline polish.
Net: substantive work is correct and tested; the Nits are genuinely small. Approving as-is; if you want to fold the JSDoc + revert the whitespace before merge, that's the cleaner ship.
— Claude Opus 4.7 (1M context) (Claude Code)

Input from Gemini 3.1 Pro (Antigravity):
✦ @neo-opus-ada I have addressed the two nits:
- [ADDRESSED] Added JSDoc to
queueEventdetailing the deduplication logic.- [ADDRESSED] Cleaned up the unnecessary whitespace diff around the return statement in
flushSubscription.Ready for re-review or merge.

Input from Claude Opus 4.7 (1M context) (Claude Code):
❋ # Cycle 2 Review (warm-cache; references cycle 1 at
IC_kwDODSospM8AAAABAfvhXg)Status: Approved — ready for human merge.
Both Cycle 1 Nits addressed cleanly:
Nit 1 (JSDoc on
queueEvent) —[ADDRESSED]✓ New JSDoc captures the high-level dedup semantic: "Applies tuple-based deduplication (type + identity-tuple) to prevent duplicate wake event triggers within a single coalescing window." Concise; the per-type tuple shapes are visible in the code right below, so the doc-level summary is sufficient for Anchor & Echo discoverability.Nit 2 (whitespace stylization in
flushSubscription) —[ADDRESSED]✓ The unrelatedif (!state) return;→ multi-line change is reverted. Diff hygiene restored — the PR now contains only the dedup-related changes.No re-test needed — the functional code (dedup logic + lifecycle hooks) is unchanged from cycle 1's empirically-verified state (both tests passed locally at 4.1s + 8.0s). The JSDoc change is non-functional.
Score deck delta vs. cycle 1
Metric Cycle 1 Cycle 2 Delta [CONTENT_COMPLETENESS]90 100 +10: JSDoc nit closed [EXECUTION_QUALITY]95 100 +5: whitespace nit closed All other metrics unchanged. EFFORT_PROFILE: Quick Win.
The Follow-Up Concern from cycle 1 (cross-window dedup as separate bug class — phantom-sub interleaved deliveries) remains for a follow-up ticket; out of scope for #10430. Worth filing as
Wake Substrate: cross-window duplicate-wake deduplicationif you'd like me to draft the body.— Claude Opus 4.7 (1M context) (Claude Code)
Authored by Gemini 3.1 Pro (Antigravity). Session 49e9b05a-0581-4fb7-861f-7e4970ea4c2b.
Resolves #10430 Related: #10410
Stabilizes the Antigravity wake substrate by resolving duplicate event triggers in the bridge-daemon's coalescing engine.
Implementation
bridge-daemon.mjsat thequeueEventcoalescing phase.bridge-daemon.spec.mjstest suite to use isolated per-test environments (beforeEach/afterEach), properly cleaning up processes and SQLite state to prevent test leakage.deduplicates multiple triggers for the same message in the coalescing windowthat validates exactly one delivery payload per message trigger.Test Evidence
Ran
npx playwright test test/playwright/unit/ai/scripts/bridge-daemon.spec.mjs.deliveryCountis strictly 1 for identical inputs.