[PR #3767] [WIP] Fix multiple firing of plugin events #10754

Closed
opened 2026-02-16 18:15:30 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/3767

State: closed
Merged: No


Fix plugin events being fired multiple times

Root cause analysis:

  • Plugin.init() is called once per directory (cached in Instance)
  • However, Bus.subscribeAll() in Plugin.init() adds a NEW subscription each time it's called
  • The subscription callback is pushed to an array without checking if it already exists
  • This causes events to be fired multiple times (once per subscription)

Solution implemented:

  • Add a subscribed flag to track if subscription is already set up in plugin state
  • Only call Bus.subscribeAll() once per plugin instance by checking the flag
  • Add comment explaining the guard to prevent future issues
  • Run typecheck to ensure no type errors
  • Clean up temporary changes

The fix ensures that Plugin.init() is idempotent - calling it multiple times will only set up the event subscription once, preventing duplicate event firing.

Changes made:

  1. Modified packages/opencode/src/plugin/index.ts:
    • Added subscribed: false flag to plugin state
    • Guard Bus.subscribeAll() call with this flag to prevent multiple subscriptions
    • Added comment explaining the guard for future maintainers

This minimal change solves the issue reported in #[issue_number] where plugin events were being fired multiple times for each event.

Original prompt

This section details on the original issue you should resolve

<issue_title>plugin events are fired multiple times</issue_title>
<issue_description>### Description

Happening since the update - each plugin event is fired multiple times

OpenCode version

v1.0.11

Steps to reproduce

  1. Create a simple plugin that writes the event type into a file.
export const NotificationPlugin: Plugin = async ({ client, $ }) => {
  return {
    event: async ({ event }) => {
      await $`echo ${event.type} >> /tmp/events.txt`;
    },
  }
}
  1. Run opencode, and start a conversation. e.g. hi

  2. Look at the file in /tmp/events.txt

This is the output:

session.created
session.created
session.created
session.created
session.created
session.created
session.created
session.created
session.created
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
session.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
message.part.updated
...

</details>

- Fixes sst/opencode#3744

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/3767 **State:** closed **Merged:** No --- Fix plugin events being fired multiple times Root cause analysis: - Plugin.init() is called once per directory (cached in Instance) - However, Bus.subscribeAll() in Plugin.init() adds a NEW subscription each time it's called - The subscription callback is pushed to an array without checking if it already exists - This causes events to be fired multiple times (once per subscription) Solution implemented: - [x] Add a `subscribed` flag to track if subscription is already set up in plugin state - [x] Only call Bus.subscribeAll() once per plugin instance by checking the flag - [x] Add comment explaining the guard to prevent future issues - [x] Run typecheck to ensure no type errors - [x] Clean up temporary changes The fix ensures that `Plugin.init()` is idempotent - calling it multiple times will only set up the event subscription once, preventing duplicate event firing. Changes made: 1. Modified `packages/opencode/src/plugin/index.ts`: - Added `subscribed: false` flag to plugin state - Guard `Bus.subscribeAll()` call with this flag to prevent multiple subscriptions - Added comment explaining the guard for future maintainers This minimal change solves the issue reported in #[issue_number] where plugin events were being fired multiple times for each event. <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>plugin events are fired multiple times</issue_title> <issue_description>### Description Happening since the update - each plugin event is fired multiple times ### OpenCode version v1.0.11 ### Steps to reproduce 1. Create a simple plugin that writes the event type into a file. ``` export const NotificationPlugin: Plugin = async ({ client, $ }) => { return { event: async ({ event }) => { await $`echo ${event.type} >> /tmp/events.txt`; }, } } ``` 2. Run opencode, and start a conversation. e.g. `hi` 3. Look at the file in /tmp/events.txt This is the output: ``` session.created session.created session.created session.created session.created session.created session.created session.created session.created session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated session.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated message.part.updated ... </details> - Fixes sst/opencode#3744 <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
yindo added the pull-request label 2026-02-16 18:15:30 -05:00
yindo closed this issue 2026-02-16 18:15:30 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#10754