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

Open
opened 2026-02-16 18:10:24 -05:00 by yindo · 1 comment
Owner

Originally created by @ErcinDedeoglu on GitHub (Feb 5, 2026).

Originally assigned to: @rekram1-node on GitHub.

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
Originally created by @ErcinDedeoglu on GitHub (Feb 5, 2026). Originally assigned to: @rekram1-node on GitHub. ## 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:** ```json { "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"`: ```typescript 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: ```typescript 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: ```typescript 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: ```bash npm install oc-solomemory@dev --prefix ~/.cache/opencode ``` ## Environment - OpenCode: latest - OS: Linux (ARM64) - Plugin: `oc-solomemory@dev`
Author
Owner

@github-actions[bot] commented on GitHub (Feb 5, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #6774: Plugins with implied latest version don't auto-update on restart
  • #10546: Plugin updates: No way to get latest version after initial install
  • #10355: opencode.json plugins version overwritten on startup

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Feb 5, 2026): This issue might be a duplicate of existing issues. Please check: - #6774: Plugins with implied latest version don't auto-update on restart - #10546: Plugin updates: No way to get latest version after initial install - #10355: opencode.json plugins version overwritten on startup Feel free to ignore if none of these address your specific case.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#8620