anomalyco / anomalyco/opencode

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

Open
#48,121 1 comment 0 reactions 1 assignee View on GitHub

@neriousy is already working on this.

Since Sep 9, 2026.

2.0
Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.