Skip to content

Plugins with npm dist-tags other than @latest never update on restart #12312

@ErcinDedeoglu

Description

@ErcinDedeoglu

Problem

When a plugin is configured with an npm dist-tag other than @latest (e.g., @dev, @next, @canary, @beta), OpenCode locks it to the first resolved version and never checks for updates on subsequent startups.

Config:

{
  "plugin": [
    "oh-my-opencode@latest",
    "oc-solomemory@dev"
  ]
}

Expected: Both plugins check the registry on startup and update if a newer version is published under their respective tag.

Actual: @latest correctly checks for updates every startup. @dev (and any non-latest tag) is cached permanently and never re-resolved — even if a newer version is published under the dev tag on npm.

Root Cause

In packages/opencode/src/bun/index.ts, the install logic only checks the registry for the literal string "latest":

if (!modExists || !cachedVersion) {
  // continue to install
} else if (version !== "latest" && cachedVersion === version) {
  return mod  // ← Any non-"latest" tag: string match against cache, return immediately
} else if (version === "latest") {
  const isOutdated = await PackageRegistry.isOutdated(pkg, cachedVersion, Global.Path.cache)
  if (!isOutdated) return mod  // ← Only "latest" checks the registry
}

After the first install, package.json in the cache stores the tag name (e.g., "dev") as the dependency value. On subsequent startups: cachedVersion === version"dev" === "dev"true → returns cached module immediately without ever checking the registry.

Meanwhile, @latest goes through PackageRegistry.isOutdated() which queries bun info {pkg} version and does a semver comparison — so @latest always gets updates.

Suggested Fix

npm dist-tags are mutable pointers, not fixed versions. All dist-tags should be re-resolved on startup, not just "latest". A simple heuristic:

function isDistTag(version: string): boolean {
  // Dist-tags are simple lowercase strings: "latest", "dev", "next", "canary", "beta"
  // Actual versions contain dots/digits: "1.0.0", "^1.0.0", "1.0.0-dev.abc123"
  return /^[a-z][a-z0-9-]*$/i.test(version) && !semver.valid(version)
}

Then in the install logic:

if (!modExists || !cachedVersion) {
  // continue to install
} else if (isDistTag(version)) {
  // Always check registry for dist-tags (@latest, @dev, @next, etc.)
  const isOutdated = await PackageRegistry.isOutdated(pkg, cachedVersion, Global.Path.cache)
  if (!isOutdated) return mod
  log.info("Cached version is outdated, proceeding with install", { pkg, cachedVersion })
} else if (cachedVersion === version) {
  return mod  // Exact pinned version, use cache
}

Note: PackageRegistry.isOutdated() currently resolves only the latest tag via bun info {pkg} version. For non-latest tags, it would need to resolve the specific tag (e.g., bun info {pkg}@dev version or query npm view {pkg} dist-tags).

Workaround

Force-update manually:

npm install oc-solomemory@dev --prefix ~/.cache/opencode

Environment

  • OpenCode: latest
  • OS: Linux (ARM64)
  • Plugin: oc-solomemory@dev

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