Skip to content

Fix double unsubscribe removing other bus handlers - #784

Open
keshavojha11 wants to merge 1 commit into
rowboatlabs:mainfrom
keshavojha11:fix/bus-double-unsubscribe
Open

Fix double unsubscribe removing other bus handlers#784
keshavojha11 wants to merge 1 commit into
rowboatlabs:mainfrom
keshavojha11:fix/bus-double-unsubscribe

Conversation

@keshavojha11

Copy link
Copy Markdown

Summary

InMemoryBus.subscribe() returned an unsubscribe closure that removed the handler with an unguarded splice:

this.subscribers.get(runId)!.splice(this.subscribers.get(runId)!.indexOf(handler), 1);

When the handler was no longer present — e.g. unsubscribe() called twice — indexOf returned -1, so splice(-1, 1) removed the last subscriber for that runId instead. That silently drops a live, unrelated handler, which stops receiving run events.

The fix guards the index before splicing, matching the pattern already used by the other buses in this package (e.g. background-tasks/bus.ts):

return () => {
    const handlers = this.subscribers.get(runId);
    if (!handlers) return;
    const idx = handlers.indexOf(handler);
    if (idx >= 0) handlers.splice(idx, 1);
};

Fixes #491.

Testing

Added a focused regression test at apps/x/packages/core/src/application/lib/bus.test.ts:

  • Subscribes handlers A and B to the same runId.
  • Calls A's unsubscribe function twice, then publishes an event.
  • Asserts B still receives the event and A does not.

Behavior:

  • Before the fix: the test fails — the second unsubscribe() call runs splice(-1, 1) and removes B, so B receives nothing (AssertionError: expected [] to have a length of 1 but got +0).
  • After the fix: the test passes — the double-unsubscribe is a no-op and B still receives the event.

Commands run:

  • vitest run src/application/lib/bus.test.ts → 1 passed (was 1 failed before the fix)
  • pnpm typecheck → clean, exit 0

Scope

  • Only apps/x/packages/core/src/application/lib/bus.ts and its new test file are touched.
  • The identical bug in the CLI's apps/cli/src/application/lib/bus.ts (CLI Bus.unsubscribe has same splice(-1) bug as desktop app #492) was intentionally left untouched — not in scope for this PR.
  • No unrelated refactors or cleanup.

InMemoryBus.subscribe returned an unsubscribe closure that ran
splice(indexOf(handler), 1) without guarding against indexOf === -1.
Calling unsubscribe twice (or after the handler was already removed)
made indexOf return -1, so splice(-1, 1) removed the last remaining
handler instead. Guard the index before splicing.

Adds a focused vitest regression test.
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.

Bus.unsubscribe removes wrong handler when called twice

1 participant