Skip to content

[BUG] plugin package.json drifts from config: stale deps never cleaned, missing deps cause slow startup #30526

@wjiuxing

Description

@wjiuxing

Description

OpenCode manages plugins through two paths that are poorly synchronized:

Path 1: opencode plugin install --global — Uses @npmcli/arborist reify with save: true to append the plugin to package.json. This is append-only: it never removes entries for plugins that were previously installed but are no longer in config.

Path 2: Direct config edit + restart — Users edit opencode.json/tui.json directly and restart. On startup, Loader.resolve() calls Npm.add() for each plugin individually, which calls reify() per-plugin (no batch). If node_modules is missing or stale, each plugin triggers a separate npm resolve+install.

Over time, package.json drifts from the actual plugin declarations in config:

  1. Stale deps remain — e.g. replacing @tarquinen/opencode-dcp with open-mem in config leaves the old entry in package.json forever
  2. Missing deps — plugins added directly to config JSON are never added to package.json; startup must resolve them from scratch each time
  3. Slow startup — each plugin calls Npm.add() → individual reify(), N plugins = N sequential npm operations instead of one batch install
  4. No plugin remove command — there is no way to cleanly uninstall a plugin from both config and package.json

Reproduction

Scenario A (CLI path — stale entries):

  1. opencode plugin install --global some-plugin@1.0.0package.json gets {"some-plugin": "1.0.0"}
  2. opencode plugin install --global replacement@2.0.0 → config is updated, but some-plugin stays in package.json
  3. Repeat → package.json accumulates orphaned deps

Scenario B (config edit path — missing entries + slow startup):

  1. Edit ~/.config/opencode/opencode.json: add "new-plugin@^1.0.0" to plugin array
  2. Delete node_modules (or start fresh)
  3. Restart opencode → startup takes seconds as each plugin is resolved individually via Npm.add()reify()
  4. After startup, package.json may still not reflect all declared plugins (only the ones that needed install)

Root Cause (source analysis)

File Behavior
packages/core/src/npm.ts L78-108 reify() uses save: true, saveType: "prod" — append-only, no prune
packages/core/src/npm.ts L138-186 install() checks declared vs locked but never removes stale entries
packages/opencode/src/plugin/install.ts patchPluginList() only modifies config JSON (opencode.json/tui.json), never package.json
packages/opencode/src/plugin/loader.ts L94 Each plugin calls resolvePluginTarget()Npm.add() → individual reify()
packages/opencode/src/cli/cmd/plug.ts No remove/uninstall subcommand for plugins

Suggested Fix

A. Sync package.json from config on startup — Before the per-plugin resolve loop, rebuild package.json dependencies to match the union of all plugin specifiers in opencode.json + tui.json, then run a single batch reify(). This would:

  • Remove stale entries
  • Add missing entries
  • Replace N individual reify calls with one batch install

B. Add opencode plugin remove <module> command — Removes plugin from config JSON and prunes package.json entry. Gives users explicit control.

Workaround

Users experiencing long "loading plugins" delays on startup can manually clean up:

# Regenerate package.json from config, then batch install (eliminates the slow per-plugin resolve)
cd ~/.config/opencode
rm -rf node_modules package.json package-lock.json
# Restart opencode — it will regenerate from config declarations

Environment

  • OpenCode: v1.15.13
  • OS: macOS
  • Config: global (opencode.json + tui.json)
  • 8 plugins across opencode.json + tui.json

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions