Skip to content

Plugin instances accumulate across config reloads (no teardown) — system-reminder injected N times #54140

Open

Description

@Vcza5

Description

After one or more plugin config reloads during the background service's lifetime, the service accumulates plugin instances and never tears down the old ones. Every event (e.g. session.created) is then delivered once per accumulated instance.

Core opencode.plan subscribes to session.created and injects the Plan-mode <system-reminder>. Result: the same reminder is persisted N times (N = instance count), byte-identical, 1–2 ms apart. Observed N from 1 to 18 over a single day; stable repro at N=4.

Not plan-specific: any plugin with event handlers fires N times. A third-party plugin's own log shows the same session.created delivered 4× within one millisecond.

Plugins

oh-my-opencode-slim + a few local .ts plugins. (The duplicate delivery affects any plugin; that one is just the consumer whose own logs exposed the multiplicity.)

OpenCode version

2.0.26 (Bun 1.4.2 runtime), Windows 10 x64

Steps to reproduce

  1. Start the app so the background service is running. Cause ≥1 plugin config reload — opencode reload, or just save any config file (file-watcher reload does the same).

  2. Run a headless prompt against the live service:
    opencode run --agent plan -m <model> --title T --auto "Reply exactly: T"

  3. Count identical reminders in the new session:

    SELECT json_extract(data,'$.text'), COUNT(*)
    FROM session_message WHERE session_id='<id>' AND type='synthetic'
    GROUP BY 1;

Dose–response on the same service, one change at a time:

change plugin instances in service identical reminders in next session
baseline 4 4
opencode reload 5 (new instance added, none destroyed) 5
opencode service restart 1 1
restarting only the terminal/TUI tab unchanged unchanged

Control: the same run with --standalone (private server) → 1.

Screenshot and/or share link

n/a — counts above are from the local session DB.

Additional probe evidence
  • Temporary probe plugin counted distinct plugin instances inside the service process: 4 at baseline; every session.created was delivered 4× within 1–2 ms, each delivery injecting exactly 1 reminder → 4 identical synthetic rows, same payload, same md5.
  • A new instance appears on each reload (probe SETUP logged at the reload instant); old instances keep receiving events (no teardown). Watcher leak is visible too: the same config file is watched 6×; deleted plugin files are still watched.
  • Distribution of reminder multiplicity across sessions that day: {1:8, 2:7, 3:2, 4:6, 6:3, 9:1, 12:1, 14:1, 18:1} — non-monotonic, consistent with per-process accumulation over a long-lived service.

Suggested fix: on reload, dispose previous instances/subscriptions before activating new ones (idempotent activation keyed by plugin id); and/or make stale instances stop receiving events.

Related: #46217 (similar symptom family), #52965, #47727 (related instance-accumulation class), #42764 (closed; different cause).

Activity

  1. opencode-agent commented on Oct 9, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report and the probe evidence.

    I tried to reproduce this on Linux with 2.0.26 and the background service. I used a local probe plugin that logs a per-instance id on setup, dispose and every event, with oh-my-opencode-slim also loaded. After each of these I ran opencode run --agent plan --auto ...:

    • opencode reload several times
    • saving the project and global opencode.json
    • editing the plugin file
    • a burst of all of these at once
    • sessions in several project directories

    In every case the old plugin instance was disposed before the new one was set up. session.created reached exactly one instance, and each new session had exactly one Plan-mode <system-reminder>.

    So this looks specific to Windows, possibly to how file watching or paths behave there. That would fit the same config file being watched 6× in your logs. I can't test Windows here, so a maintainer with a Windows machine will need to look at it.

    If you can, it would help to add:

    • the service log lines (level=INFO ... message="watcher started" / "watcher subscribe") around a single opencode reload that adds an instance
    • whether any of your config or plugin paths are reached in more than one way, e.g. with different drive-letter case, a symlink or junction, or / vs \ separators

    Related issues, most likely first:

  2. Vcza5 commented on Oct 9, 2026

    @Vcza5
    Author

    Follow-up with the two things you asked for.

    1. Log lines around a single opencode reload that adds an instance

    Repro: duplicate count in the next session went 1 → 2 with this reload. All lines role=server, ~1s window:

    13:22:18.718 WARN  configuration normalization diagnostic source="...\.config\opencode\opencode.json"
    13:22:18.721 INFO  watcher subscribe path="C:\Users\<user>\.config\opencode"       type=directory ignores=3
    13:22:18.721 INFO  watcher subscribe path="C:\Users\<user>\.opencode"             type=directory ignores=3
    13:22:18.721 INFO  watcher subscribe path="C:\"                                   type=entries
    13:22:18.721 INFO  watcher subscribe path="C:\Users"                              type=entries
    13:22:18.721 INFO  watcher subscribe path="C:\Users\<user>"                       type=entries
    13:22:18.740 INFO  msg="loading plugin" id="...\plugins\auto-ocr-image.ts"
    13:22:18.742 INFO  watcher subscribe path="...\plugins\auto-ocr-image.ts"         type=file
    13:22:18.742 INFO  msg="loading plugin" id="...\plugins\notify.ts"
    13:22:18.744 INFO  watcher subscribe path="...\plugins\notify.ts"                 type=file
    13:22:18.745 INFO  msg="loading plugin" id="...\plugins\skill-stats.ts"
    13:22:18.746 INFO  watcher subscribe path="...\plugins\skill-stats.ts"            type=file
    13:22:18.747 INFO  msg="loading plugin" id="...\plugins\tool-description-boundary.ts"
    13:22:18.748 INFO  watcher subscribe path="...\plugins\tool-description-boundary.ts" type=file
    13:22:18.749 INFO  msg="loading plugin" id="...\plugins\tree-tools.ts"
    13:22:18.750 INFO  watcher subscribe path="...\plugins\tree-tools.ts"             type=file
    13:22:18.753 INFO  msg="loading plugin" id=opencode-cmd-provider@2.1.6
    13:22:18.754 INFO  msg="loading plugin" id=opencode-cmd-provider@2.1.6   <-- same id loaded twice
    13:22:19.347 INFO  watcher subscribe path="...\.config\opencode\AGENTS.md"        type=file
    13:22:19.347 INFO  watcher subscribe path="...\AGENTS.md"                         type=file
    

    Window totals: watcher subscribe = 18, watcher stopped = 1, msg="loading plugin" = 9.

    Two things stand out.

    (a) The same absolute paths are re-subscribed many times inside one server process (no process restart in between):

    path watcher subscribe count within this server run
    ...\.config\opencode\skill, ...\skills, ...\.opencode\skill, ...\.opencode\skills 49 each
    ...\.config\opencode\AGENTS.md, ...\AGENTS.md 27 each
    each ...\.config\opencode\plugins\*.ts 10 each
    C:\, C:\Users, C:\Users\<user> (entries) 10 each

    (b) plugin reconciliation runs constantly with a monotonically increasing id: 3080 reconciliation completed lines in the log; ids 35–40 all completed within the same second.

    2. Path alias check (your second question): negative

    • No reparse points anywhere under ~/.config/opencode, ~/.opencode, ~/.cache/opencode, ~/.local/share/opencode — checked file attributes and fsutil reparsepoint query. No symlinks/junctions.
    • Every watcher path in the log is canonical C:\Users\<user>\...: uppercase drive letter, \ separators. No lowercase-drive form, no / form, no 8.3 short form (~1) ever appears in the logs (though all three resolve on this machine). Env vars are not set (OPENCODE_CONFIG / XDG_CONFIG_HOME empty).
    • The only "one thing, several paths" cases are genuinely distinct files (~\AGENTS.md, ~\.config\opencode\AGENTS.md, ~\AppData\...\AGENTS.md), each subscribed separately — expected for instruction discovery.

    So the 49× subscription is not an alias artifact: identical absolute paths get subscribed repeatedly within one process.

    3. Updated dose–response (same live service, opencode service restart between sessions)

    action identical reminders in next session
    fresh service, baseline 1
    fresh service, after one opencode reload 2
    later in that process: baseline 4
    later: after one opencode reload 5
    later: after a second opencode reload 5

    Location-scoped: at one moment, a session opened in a brand-new temp directory got 1 reminder while a session in the home directory got 3 — same service process. So multiplicity is per-location, not global.

    4. Counter-finding (may help triage)

    Ordinary file writes cause heavy watcher churn but did not change the duplicate count in our samples:

    action Δ duplicates watcher churn
    create / rewrite / delete a plugin .ts 0 heavy (up to 50 loading plugin, 39 re-subscribes)
    semantic edit of opencode.json (changed shell) 0 moderate (27 loading plugin)
    byte-identical rewrite of opencode.json 0 none
    unrelated file created in config root 0 none
    touch AGENTS.md 0 none
    opencode reload +1 18 re-subscribes, 4 stopped

    So on Windows the explicit opencode reload path reliably bumps the count in our measurements, while file writes mostly churn watchers. The original "N grew to 18 over a day of config editing" correlation is real, but we cannot currently say which edit produced it — the instrumenting probe plugin has been removed. I can re-add a probe and run a longer session if that would help narrow it down.

    Env: v2.0.26, Windows 10 x64, background service (opencode serve --service), Bun 1.4.2.

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