Skip to content

[Bug]: Plugin and provider loaders cache the same package under two keys and never refresh an existing install #48514

Description

@depler

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:

  • plugin loader appends @latest:

    // packages/opencode/src/plugin/shared.ts
    const pkg = hit?.name && hit.raw === hit.name ? `${hit.name}@latest` : spec
  • provider loader uses model.api.npm verbatim (whatever the plugin/provider declared):

    // packages/opencode/src/provider/provider.ts
    const item = await Npm.add(model.api.npm)

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

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions