Summary
Opencode can keep loading an old version of a plugin/provider package even though npm reports the latest version as installed. Two things combine: the cache is keyed by the exact specifier string, and Npm.add returns an existing directory without checking whether it is still current.
Environment
- OS: Ubuntu 24.04.4 LTS
- opencode: 1.18.30
- relevant package:
opencode-cmd-provider (@latest dir at 1.7.3, bare dir at 1.6.2; npm reports 1.7.3 as latest)
Problem
1. Cache is keyed by the exact specifier string
Npm.add derives the cache directory from the raw package string:
// packages/core/src/npm.ts
const directory = (pkg: string) => path.join(global.cache, "packages", sanitize(pkg))
The two loaders pass different strings for the same package:
So a single plugin that registers its provider by bare name produces two directories:
~/.cache/opencode/packages/opencode-cmd-provider@latest # plugin
~/.cache/opencode/packages/opencode-cmd-provider # runtime provider
2. Existing installs are never refreshed
// packages/core/src/npm.ts
if (yield* afs.existsSafe(path.join(dir, "node_modules", name))) {
return resolveEntryPoint(name, path.join(dir, "node_modules", name))
}
Once the bare directory exists it is returned forever, at the version it was first installed. Updating the package moves only the @latest copy forward; the bare copy keeps the old version and is still what opencode loads at runtime.
Impact
The effective state is confusing and user-visible: npm reports the latest version as installed, but opencode runs the old one. Anything that depends on the two halves of one plugin matching — capability declarations vs. the code that enforces them, feature additions, bug fixes — can silently use stale behavior. Deleting the stale directory forces opencode to re-install the current version, which confirms the cause.
Why the plugin loader / provider loader should be reconciled
A plugin and the provider it registers are conceptually one dependency. Keying them separately and never reconciling versions makes divergence possible for any plugin that registers a provider by an unpinned (or differently formatted) specifier. A plugin author can work around this by pinning a version, but opencode should not depend on that discipline to keep a plugin internally consistent.
Possible directions
- Key the cache by resolved package name (or name + resolved version), so
foo and foo@latest resolve to the same installed artifact instead of two.
- Have
Npm.add verify the installed version against the requested spec and re-resolve when it no longer satisfies it, instead of short-circuiting on mere existence.
- Ensure a plugin's runtime provider is loaded from the same resolved package the plugin came from.
Related
Plugin-side report (unpinned provider specifier makes this reachable): rashidrazak/opencode-cmd-provider#149
Summary
Opencode can keep loading an old version of a plugin/provider package even though
npmreports the latest version as installed. Two things combine: the cache is keyed by the exact specifier string, andNpm.addreturns an existing directory without checking whether it is still current.Environment
opencode-cmd-provider(@latestdir at 1.7.3, bare dir at 1.6.2;npmreports 1.7.3 as latest)Problem
1. Cache is keyed by the exact specifier string
Npm.addderives the cache directory from the raw package string:The two loaders pass different strings for the same package:
plugin loader appends
@latest:provider loader uses
model.api.npmverbatim (whatever the plugin/provider declared):So a single plugin that registers its provider by bare name produces two directories:
2. Existing installs are never refreshed
Once the bare directory exists it is returned forever, at the version it was first installed. Updating the package moves only the
@latestcopy forward; the bare copy keeps the old version and is still what opencode loads at runtime.Impact
The effective state is confusing and user-visible:
npmreports the latest version as installed, but opencode runs the old one. Anything that depends on the two halves of one plugin matching — capability declarations vs. the code that enforces them, feature additions, bug fixes — can silently use stale behavior. Deleting the stale directory forces opencode to re-install the current version, which confirms the cause.Why the plugin loader / provider loader should be reconciled
A plugin and the provider it registers are conceptually one dependency. Keying them separately and never reconciling versions makes divergence possible for any plugin that registers a provider by an unpinned (or differently formatted) specifier. A plugin author can work around this by pinning a version, but opencode should not depend on that discipline to keep a plugin internally consistent.
Possible directions
fooandfoo@latestresolve to the same installed artifact instead of two.Npm.addverify the installed version against the requested spec and re-resolve when it no longer satisfies it, instead of short-circuiting on mere existence.Related
Plugin-side report (unpinned provider specifier makes this reachable): rashidrazak/opencode-cmd-provider#149