Skip to content

core: concurrent location plugin reloads crash with r.base.get #48121

Closed

Description

@DDwsgood

Description

An intermittent server-side plugin reload failure can occur when one local plugin change reloads the same configured plugins in multiple active locations. In one observed case, the per-plugin load lines for two locations were interleaved, then both activations failed with the same Effect Context lookup defect:

09:07:37.909 loading csu-thinking-fix.ts       span=201301
09:07:37.910 loading csu-thinking-fix.ts       span=201215
09:07:37.910 loading opencode-enhanced-subagent span=201302
09:07:37.910 loading opencode-enhanced-subagent span=201215
...
09:07:37.942 failed to reload plugins Cause([Die(TypeError: undefined is not an object (evaluating 'r.base.get')),Die(...)])
09:07:37.962 failed to reload plugins Cause([Die(TypeError: undefined is not an object (evaluating 'r.base.get')),Die(...)])

The corresponding completion events identify the two locations as <home> and <home>/opencode-v2-plugins. This is therefore not evidence that the semaphore inside one plugin registry was bypassed: each location has its own registry and lock. It is evidence that separate location activations were running at the same time. It is also not the immediate/install two-pass path, because the two plugin sequences alternate item by item instead of appearing as two complete sequential blocks.

The retained log contains 19 occurrences of the exact same signature on beta-19271. I do not yet have a deterministic standalone reproducer, so the frequency and root cause below should be treated as observed/inferred rather than guaranteed.

Impact was larger than one failed reload. After a four-location failure at 09:02:43, two sessions failed at 09:02:46 with:

Session.AgentNotFoundError: Agent not found: "deepseek-v4-flash"
SessionRunnerModel.ModelUnavailableError: Model unavailable: litellm/glm-5.3-flash

A manual service restart at 09:04 restored normal plugin loading. I have not established that a later activation can never recover without a restart.

Source observations at 9128e847bdbf9e5a58b7bdc33896e484e745aedd:

  • packages/core/src/plugin/module.ts:16-45,80-105 watches local module graphs and publishes reload requests.
  • packages/core/src/plugin/supervisor.ts:138-195,205-237 applies generations and accepts source/module, 24-hour, config/SDK, and package-update triggers. The periodic update-check apply is forked, while the main stream serializes its own activations.
  • packages/core/src/plugin.ts:26,89-179 has a semaphore per registry. Inside State.batch, it removes/closes the old suffix before loading replacements (:125-170) and updates inventory only afterward (:172).
  • packages/core/src/state.ts:86-109 explicitly says a batch coalesces notifications but rolls nothing back.
  • packages/core/src/plugin/supervisor.ts:143-152 stamps internal plugins with revision "internal"; packages/core/src/plugin/internal.ts:227-245 places config agent/provider plugins in post, after instance/package plugins.
  • The minified r.base.get expression matches Effect 4.0.0-rc.112 Context.lookup (effect/src/Context.ts:681-696, especially impl.base.get(key) at :686). Both checked OpenCode revisions resolve Effect to 4.0.0-rc.112.

The exact point at which the activation exits is uncertain. However, closing the old suffix before the replacement generation is fully established means any unexpected exit can expose a partial generation because State.batch is not transactional. I could not confirm the more specific theory that a closed scope remains as an unchanged (id, revision) slot: active.delete(id) occurs before Scope.close, so the current code should normally force a suffix mismatch on the next activation.

Possible directions:

  1. Prevent unsafe overlap between location plugin activations, or stop sharing any mutable Effect context involved in those activations. The two locations need coordination broader than the per-registry semaphore.
  2. Keep the old generation alive until all replacement scopes have loaded successfully, then swap and close the old scopes. This would limit damage even if the Effect defect has another cause.
  3. After any failed registry.activate, force a full retry/reconciliation rather than relying only on the normal id/revision prefix comparison.

No related semantic fix was found between c72b535dee and 9128e847bd; the only later relevant supervisor change was 8da7526616, which added dependency-aware Core local-plugin reloads and the modules.changes() trigger.

Plugins

Local plugins plus opencode-bash-classifier@0.7.3-v2 and opencode-usage-stat@2.4.5. The same configured set was active in more than one location.

OpenCode version

opencode2 v0.0.0-beta-19271 (beta channel), source revision checked against 9128e847bdbf9e5a58b7bdc33896e484e745aedd.

Steps to reproduce

This is an observed intermittent reproduction, not yet a deterministic minimal test:

  1. Run the beta service with at least two active locations that share configured local and package plugins.
  2. Edit a source file in one configured local plugin. In the captured case, one Node script performed one write to opencode-dsh-godmode/src/index.ts at 09:07:37.762Z.
  3. Watch the server log. Around 147 ms later, plugin load sequences for two locations interleave.
  4. Observe failed to reload plugins with the r.base.get cause. Depending on where activation fails, config-provided agents/providers may then be unavailable.
  5. Restart the service; the observed affected agents/providers return.

Expected: each location reload completes independently, and a failed replacement leaves the prior complete generation active.

Actual: concurrent location reloads intermittently fail in Effect context lookup, and the failed generation can leave agent/provider functionality unavailable until another successful reconciliation or restart.

Screenshot and/or share link

No screenshot. The short log excerpt above is from the local server log; full logs can be provided if needed.

Operating System

WSL2 Ubuntu, Linux 6.18.33.2-microsoft-standard-WSL2 x86_64.

Terminal

TERM=xterm-256color, shell /bin/bash.

Local verification notes

  • Confirmed all 19 beta-19271 errors in the retained server log have exactly the same cause.
  • Confirmed the 09:07 reload followed a real local plugin source write; it was not pure session activity or a config-file edit.
  • Confirmed the interleaved loads terminate in separate location events/errors.
  • Confirmed restart recovery at 09:04 from the service log.
  • A watchdog later reported recovery after 25 seconds, but it started after all 19 historical errors and initially counted those old lines as new. That result is not valid evidence of self-healing.
  • Reports of six identical failures on beta-19151 could not be independently verified from the currently retained logs and are intentionally omitted from the main claim.

Activity

  1. DDwsgood commented on Sep 9, 2026

    @DDwsgood
    Author

    Related reports in the same subsystem, found while checking for duplicates (none is a duplicate of this one, but the family may be useful for triage):

  2. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the very detailed report. I reproduced this on 0.0.0-beta-19275, the closest published build to your beta-19271.

    Setup:

    • Six small local plugins in ~/.config/opencode/plugins/
    • Your two packages: opencode-bash-classifier@0.7.3-v2 and opencode-usage-stat@2.4.5
    • One agent defined in the config
    • Two booted locations: home and a project inside it

    I then edited the local plugin files repeatedly. The reloads for the two locations interleaved, and I got dozens of failed to reload plugins errors with exactly your r.base.get cause. After that, the agent from the config was missing in both locations until a restart.

    The same script on the latest dev (d1bd20f) and on the 2.2.0 release gave no failed reloads. All agents stayed available in both locations, and on 2.2.0 both package plugins reloaded cleanly each time. So this looks already fixed, and the fix should be in the next release if you're still on the beta channel; upgrading to 2.2.0 should already help.

    The bot will close this issue in 1 day unless you reply that it still happens, e.g. on 2.2.0 or a newer version.

  3. opencodejr commented on Oct 29, 2026

    @opencodejr

    Closing this as completed, since there's been no reply in the past day. I reproduced the concurrent location reload failure (r.base.get and agents from your config disappearing) on 0.0.0-beta-19275. It did not happen on the latest dev (d1bd20f) or on 2.2.0, so it's most likely fixed, and the fix should be in the next release. If it still happens on 2.2.0 or a newer version, please comment here or open a new issue with the server log, and we'll look again. Thanks for the thorough report!

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions