Repository navigation
Description
Version: plugin 2.0.1 · opencode: 2.1.4 (opencode serve and desktop serve --service), macOS
Summary
acquireSharedOtel pins the shared SDK singleton on globalThis.__opencode_plugin_otel_shared__ with the configKey of the first successful load in the process, and that key is never re-pinned. When opencode hot-reloads configuration (any opencode.json edit), it re-runs plugin setup once per loaded location. In a long-lived server hosting many locations, one mid-process edit to any otel-relevant option makes every subsequent load throw:
failed to load plugin devtheops.otel
Error: OpenCode OTel plugin instances must use identical telemetry configuration within one process
Timeline observed (local time, single opencode serve process, ~40 loaded locations):
- 19:23 —
opencode.jsonedited (config-sync tooling rewrites it several times through the evening) - 19:29 — first
failed to load plugin devtheops.otelafter the watcher hot-reloads config; ~40 WARNs per edit, once per location - 19:29–21:56 — 1,867 WARNs total, tracking each config edit
- 21:56 — options return to the originally-pinned values → loads succeed again, no code change
Repro (minimal)
- Start
opencode serve(any long-lived host; desktop--serviceshows the same) with@devtheops/opencode-plugin-otelenabled via globalopencode.jsonand ≥2 project locations loaded. - Edit
opencode.json, changing any otel-relevant option (e.g. append todisabledMetrics). - Watcher hot-reload →
failed to load plugin devtheops.otel … must use identical telemetry configuration within one processfor every location. - Revert the edit → loads succeed again (the pinned key matches once more). A restart is otherwise required.
Why it happens (dist/index.js, v2.0.1)
configKey()(~L70534) hashes the resolved config;acquireSharedOtel()(~L70555) pinsglobalThis.__opencode_plugin_otel_shared__on first successful load and throws on any later mismatched key — including the pending-promise path (~L70583).releaseSharedOtel()(~L70588) only decrementsrefswith aMath.max(0, …)clamp; the plugin's cleanup fn (~L72253) calls it, but the singleton and its pinnedconfigKeyare never cleared.- So the first-ever config in the process wins forever; any intentional otel config change mid-process fails all subsequent loads until restart — even though a config reload is exactly the moment an operator expects the new config to apply.
The intentional part is fine — failing on genuinely conflicting multi-location config prevents exporting one location's data using another location's settings (README, "Configuration"). The gap is that a uniform, deliberate config change can never take effect without a process restart, and the error carries no hint of which field diverged.
Suggestions
- When
refsreaches 0 inreleaseSharedOtel, flush + shut down providers and clearglobalThis[OTEL_KEY]so the next load re-pins fresh config (makes hot-reload converge). - Or on key mismatch, replace the singleton (drain old exporters, re-init with the new config) rather than throwing — a config change should win over the first-pinned snapshot.
- At minimum, include the differing field(s) or both key hashes in the error message — today the message gives nothing actionable.
Environment: macOS 15, opencode 2.1.4 (both opencode serve and desktop serve --service), plugin 2.0.1, protocol http/protobuf, single global config source.
Activity