Skip to content

MaxListenersExceededWarning after web interface starts #28492

Closed as not planned
Closed as not planned

Description

@GoldenStain

Describe the bug

When running opencode, after the web interface starts, a MaxListenersExceededWarning is printed in the terminal.

Web interface:      http://127.0.0.1:65535/
^[^[MaxListenersExceededWarning: Possible EventTarget memory leak detected. 11 event listeners added to [iz]. MaxListeners is undefined. Use events.setMaxListeners() to increase limit
 emitter: iz {
  _events: [Object ...],
  _eventsCount: 1,
  _maxListeners: undefined,
  [Symbol(kCapture)]: false,
  emit: [Function: emit],
  setMaxListeners: [Function: setMaxListeners],
  getMaxListeners: [Function: getMaxListeners],
  addListener: [Function: addListener],
  on: [Function: addListener],
  prependListener: [Function: prependListener],
  once: [Function: once],
  prependOnceListener: [Function: prependOnceListener],
  removeListener: [Function: removeListener],
  off: [Function: removeListener],
  removeAllListeners: [Function: removeAllListeners],
  listeners: [Function: listeners],
  rawListeners: [Function: rawListeners],
  listenerCount: [Function: listenerCount],
  eventNames: [Function: eventNames],
},
    type: "event",
   count: 11,

      at overflowWarning (node:events:185:19)
      at addListener (node:events:158:22)
      at ~effect/Effect/evaluate (/$bunfs/root/chunk-hcr6snjj.js:25:4492)
      at runLoop (/$bunfs/root/chunk-hcr6snjj.js:25:2045)
      at evaluate (/$bunfs/root/chunk-hcr6snjj.js:25:1435)
      at runTasks (/$bunfs/root/chunk-hcr6snjj.js:11:33531)
      at afterScheduled (/$bunfs/root/chunk-hcr6snjj.js:11:33418)

This looks like listeners may be getting added repeatedly to the same EventTarget or emitter without cleanup, or the default listener limit is being exceeded during normal startup/use.

Version

  • opencode: 1.14.50

Environment

  • OS: WSL2 Linux 6.6.87.2-microsoft-standard-WSL2 x86_64
  • Node.js: v22.22.1
  • Bun: not available in PATH

Steps to reproduce

  1. Start opencode from a terminal.
  2. Wait until the web interface URL is printed.
  3. Observe the warning in terminal output.

Expected behavior

No MaxListenersExceededWarning should be printed during normal opencode startup or use.

Additional context

The warning appeared immediately after the web interface URL was printed. The terminal output included ^[^[ immediately before the warning.

Activity

  1. github-actions commented on May 20, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If your issue is the same as one of those, please add a reaction or comment there instead so we can track it in one place.

  2. sinnce commented on Jun 28, 2026

    @sinnce

    Reproducible on 1.17.11 (and 1.17.8), and this is a real listener leak on client disconnect — not a false/benign "normal fanout" warning (cf. #23798). It is the runtime side of the now-closed #15645 / #17628, which still reproduces.

    Minimal reproduction (no plugins)

    cd <any git repo>
    opencode web --pure --port 4199 --print-logs --log-level INFO &
    sleep 3
    # open + immediately drop the event stream 16x (what a browser does on refresh/navigate/reconnect):
    for i in $(seq 1 16); do
      curl -s --max-time 1 -H "x-opencode-directory: $PWD" http://127.0.0.1:4199/event >/dev/null
    done

    Result: MaxListenersExceededWarning: Possible EventTarget memory leak detected. 11 event listeners added to [..] (event name "event", Effect evaluate stack) — identical to the report above. --pure (zero plugins) rules out any plugin cause.

    Root cause

    The --print-logs output shows 16× global event connected and 0× global event disconnected. The SSE finalizer in eventResponse() never runs on disconnect:

    packages/opencode/src/server/routes/instance/httpapi/handlers/global.ts

    const events = Stream.callback<GlobalBusEvent>((queue) => {
      const handler = (event) => Queue.offerUnsafe(queue, event)
      return Effect.acquireRelease(
        Effect.sync(() => GlobalBus.on("event", handler)),
        () => Effect.sync(() => GlobalBus.off("event", handler)),  // never runs on abort
      )
    })
    // ...
    Stream.ensuring(Effect.logInfo("global event disconnected")),   // never logs on abort

    The stream scope isn't tied to the request's abort signal, so a TCP CLOSE-WAIT (client gone) never finalizes the scope → GlobalBus.off(...) and the 10s heartbeat cleanup never fire. Each event-stream connection permanently leaks one GlobalBus "event" listener (+ its heartbeat interval). Browsers reconnect the stream routinely, so the count crosses Node's default defaultMaxListeners (10) in normal use; long sessions grow unbounded (the 7GB / unresponsive behavior in #17628).

    Note

    This is not fixed by bumping setMaxListeners — that hides a genuine unbounded leak. The fix is to interrupt the SSE stream scope when the request aborts so the existing acquireRelease / Stream.ensuring cleanup actually runs.

  3. sinnce commented on Jun 28, 2026

    @sinnce

    Cross-ref: this is the same /event SSE-disconnect listener leak now addressed by #31922 (Closes #22198) — it bounds the per-subscriber SSE backlog and ends/unsubscribes consumers stuck in CLOSE_WAIT, touching exactly server/routes/instance/httpapi/handlers/global.ts + event.ts. The mechanism there matches the repro in my comment above (N× global event connected, 0× global event disconnected). Still reproduces on 1.17.11, so worth tracking/merging under #31922.

  4. elegracer commented on Jun 30, 2026

    @elegracer

    oh come on, this should have been fixed long ago

  5. lalexisj commented on Aug 7, 2026

    @lalexisj

    Still reproduces on 1.18.15 (Linux, compiled Bun binary). This is a real per-connection listener leak, not a benign fanout warning — the same GlobalBus "event" emitter, same ~effect/Effect/evaluate stack trace, same _eventsCount: 1.

    Reproduction

    opencode web --hostname 127.0.0.1 --port 4999 &
    sleep 4
    for i in $(seq 1 12); do
      curl -s -N --max-time 5 "http://127.0.0.1:4999/event" >/dev/null 2>&1 &
    done

    The --print-logs output (or ~/.local/share/opencode/log/opencode.log) shows:

    • 12× event connected
    • 0× event disconnected

    even though all 12 clients closed their connections after 5s. MaxListenersExceededWarning fires as soon as 11 connections accumulate, exactly as in @sinnce's repro.

    Impact data from my setup

    Over ~1 month of running opencode web as a monitoring server for an agent pipeline (many opencode run --attach http://127.0.0.1:4096 sessions), the shared log shows:

    • event connected: 2458
    • event disconnected: 4

    Each attached session permanently leaks one GlobalBus listener (+ heartbeat interval + unbounded queue), so any environment where clients reconnect routinely (browser tabs, attached agents) grows the count without bound and the warning recurs constantly.

    Confirmation of root cause

    Isolated test with plain node:http + effect@4.0.0-beta.83 (the exact versions opencode bundles): the res.on("close") → fiber.interrupt → stream finalizer chain works correctly and runs GlobalBus.off. So the leak is in opencode's SSE handler wiring — the stream scope is not tied to the request abort signal, exactly as @sinnce documented. The acquireRelease finalizer in global.ts / event.ts never runs on client disconnect.

    Workaround for now: restart the web server periodically to reset listeners. setMaxListeners would only hide an unbounded leak.

    Worth re-opening/merging a fix (e.g. #31922) that binds the SSE stream scope to the request abort signal and bounds the per-subscriber backlog.

  6. disse70 commented on Aug 20, 2026

    @disse70

    Confirming this happens outside the web-interface-startup path too — same warning triggers reliably on a local stdio MCP server call, specifically at getPrompt.

    Environment

    • opencode 1.18.19, Windows 11 (Bun runtime bundled with opencode)
    • Local MCP server: @opengsd/gsd-core@1.11.0's gsd-mcp-server (stdio transport, spawned via command: ["node", "<path>/gsd-mcp-server.js"])

    Repro

    1. Register a local stdio MCP server in opencode.json (mcp.<name>.type = "local").
    2. Trigger a prompt fetch through it (in my case, a /gsd-* slash command backed by an MCP prompts/get call).
    3. Warning fires immediately, UI stalls tens of seconds, then the trace below prints and the session effectively dies.

    Trace

    MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added.
    type: "drain"
    count: 11
        at overflowWarning (node:events:185:19)
        at addListener (node:events:158:22)
        at once (node:events:200:36)
        at <anonymous> (B:/~BUN/root/chunk-vbr5bqtv.js:69:2280)
        at new Promise (1:11)
        at send (B:/~BUN/root/chunk-vbr5bqtv.js:69:2131)
        at <anonymous> (B:/~BUN/root/chunk-vbr5bqtv.js:67:14427)
        at new Promise (1:11)
        at request (B:/~BUN/root/chunk-vbr5bqtv.js:67:12703)
        at getPrompt (B:/~BUN/root/chunk-vbr5bqtv.js:67:29206)
        at ~effect/Utils/internal (B:/~BUN/root/chunk-xx30ejg4.js:8:1579)
        at <anonymous> (B:/~BUN/root/chunk-xx30ejg4.js:25:5339)
        at ~effect/Effect/evaluate (B:/~BUN/root/chunk-xx30ejg4.js:25:5625)
        at runLoop (B:/~BUN/root/chunk-xx30ejg4.js:25:2045)
        at evaluate (B:/~BUN/root/chunk-xx30ejg4.js:25:1435)
    

    (full trace continues through ~effect internals — same shape reported above)

    What I ruled out

    • Not the MCP server itself: calling it by hand over stdio with a raw initialize JSON-RPC request responds instantly and correctly, no errors.
    • Not a slow-spawn/cold-start issue: same result whether the server is launched via npx or a direct node <path> command.
    • Not specific to that MCP server's tool/prompt catalog size: disabling the MCP server entirely (mcp.<name>.enabled: false) removes the warning and the stall completely; re-enabling it (with everything else unchanged) reproduces it every time, consistently at the getPrompt call site shown in the trace.

    So this looks like listener accumulation in the client-side stdio transport (send/request in the bundled chunk) specifically on the request path used by prompts/get, not something a server implementation can work around. Happy to provide opencode.json mcp config or anything else that helps narrow it down.

  7. evannotfound commented on Aug 21, 2026

    @evannotfound

    same issue here

  8. lucaspiress commented on Aug 26, 2026

    @lucaspiress

    Seeing the identical warning on Windows 11, opencode v1.18.23 (npm global install), Node v24.18.0:

    MaxListenersExceededWarning: Possible EventTarget memory leak detected.
    11 event listeners added
        at overflowWarning (node:events)
        at addListener (node:events)
        at ~effect/Effect/evaluate
        at runLoop
        at evaluate
    

    Same "11 event listeners" count as the original report, on a different OS/Node version — suggests the count isn't strongly driven by plugin load, since I have 2 plugins active (oh-my-opencode-slim + @opengsd/gsd-core) totaling ~9 registered hook keys, and I could not reproduce it via opencode serve + session creation + SSE /event subscription in isolation — it seems to require a fuller interactive session/chat-turn lifecycle to trip. Ruled out plugin duplication as the cause on my end (confirmed via opencode debug config → plugin_origins, no duplicate registrations). Happy to provide more diagnostics if useful.

  9. quentingosset commented on Sep 20, 2026

    @quentingosset

    I can reproduce the stdio MCP / getPrompt variant described above.

    Environment:

    With this local MCP enabled, starting a new OpenCode session from Orca produces:

    MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 `drain` listeners added
    

    The trace goes through the MCP transport send / request path and then getPrompt.

    If I set "enabled": false for the MCP in the global OpenCode configuration and restart OpenCode, the warning disappears. Re-enabling the MCP brings the warning back. The MCP tools themselves are usable once the session starts.

    This looks consistent with the client-side stdio transport issue already described in this issue. The MCP appears to trigger the prompts/get path, but the listener accumulation seems to happen in OpenCode's transport rather than in the MCP server.

  10. nicholaicascio commented on Sep 26, 2026

    @nicholaicascio

    On windows:

    MaxListenersExceededWarning: Possible EventTarget memory leak detected. 11 event listeners added to [M$]. MaxListeners is undefined. Use events.setMaxListeners() to increase limit
     emitter: M$ {
      _events: [Object ...],
      _eventsCount: 1,
      _maxListeners: undefined,
      [Symbol(kCapture)]: false,
      emit: [Function: emit],
      setMaxListeners: [Function: setMaxListeners],
      getMaxListeners: [Function: getMaxListeners],
      addListener: [Function: addListener],
      on: [Function: addListener],
      prependListener: [Function: prependListener],
      once: [Function: once],
      prependOnceListener: [Function: prependOnceListener],
      removeListener: [Function: removeListener],
      off: [Function: removeListener],
      removeAllListeners: [Function: removeAllListeners],
      listeners: [Function: listeners],
      rawListeners: [Function: rawListeners],
      listenerCount: [Function: listenerCount],
      eventNames: [Function: eventNames],
    },
        type: "event",
       count: 11,
    
          at overflowWarning (node:events:185:19)
          at addListener (node:events:158:22)
          at ~effect/Effect/evaluate (B:/~BUN/root/chunk-nhtrqkcr.js:25:4492)
          at runLoop (B:/~BUN/root/chunk-nhtrqkcr.js:25:2045)
          at evaluate (B:/~BUN/root/chunk-nhtrqkcr.js:25:1435)
          at runTasks (B:/~BUN/root/chunk-nhtrqkcr.js:11:12750)
          at afterScheduled (B:/~BUN/root/chunk-nhtrqkcr.js:11:12637)
    
  11. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions