Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions src/cli/doctor/checks/system-loaded-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { join } from "node:path"
import { getLatestVersion } from "../../../hooks/auto-update-checker/checker"
import { extractChannel } from "../../../hooks/auto-update-checker"
import { PACKAGE_NAME } from "../constants"
import { getOpenCodeCacheDir, parseJsonc } from "../../../shared"
import { getOpenCodeConfigDir, parseJsonc } from "../../../shared"

interface PackageJsonShape {
version?: string
Expand All @@ -26,16 +26,6 @@ function getPlatformDefaultCacheDir(platform: NodeJS.Platform = process.platform
return join(homedir(), ".cache")
}

function resolveOpenCodeCacheDir(): string {
const xdgCacheHome = process.env.XDG_CACHE_HOME
if (xdgCacheHome) return join(xdgCacheHome, "opencode")

const fromShared = getOpenCodeCacheDir()
const platformDefault = join(getPlatformDefaultCacheDir(), "opencode")
if (existsSync(fromShared) || !existsSync(platformDefault)) return fromShared
return platformDefault
}

function readPackageJson(filePath: string): PackageJsonShape | null {
if (!existsSync(filePath)) return null

Expand All @@ -53,8 +43,26 @@ function normalizeVersion(value: string | undefined): string | null {
return match?.[0] ?? null
}

function resolvePluginInstallDir(): string {
const configDir = getOpenCodeConfigDir({ binary: "opencode", version: null })
const configInstalled = join(configDir, "node_modules", PACKAGE_NAME, "package.json")
if (existsSync(configInstalled)) return configDir

const cacheDir = join(getPlatformDefaultCacheDir(), "opencode")
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Priority inversion: XDG_CACHE_HOME environment variable is checked AFTER the default platform cache directory. Per XDG Base Directory Specification, environment variable overrides should take precedence over defaults. The function checks getPlatformDefaultCacheDir() before process.env.XDG_CACHE_HOME, meaning a stale installation in the default cache could be used even when the user has explicitly configured XDG_CACHE_HOME to a custom location.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/doctor/checks/system-loaded-version.ts, line 51:

<comment>Priority inversion: `XDG_CACHE_HOME` environment variable is checked AFTER the default platform cache directory. Per XDG Base Directory Specification, environment variable overrides should take precedence over defaults. The function checks `getPlatformDefaultCacheDir()` before `process.env.XDG_CACHE_HOME`, meaning a stale installation in the default cache could be used even when the user has explicitly configured `XDG_CACHE_HOME` to a custom location.</comment>

<file context>
@@ -53,8 +43,26 @@ function normalizeVersion(value: string | undefined): string | null {
+  const configInstalled = join(configDir, "node_modules", PACKAGE_NAME, "package.json")
+  if (existsSync(configInstalled)) return configDir
+
+  const cacheDir = join(getPlatformDefaultCacheDir(), "opencode")
+  const cacheInstalled = join(cacheDir, "node_modules", PACKAGE_NAME, "package.json")
+  if (existsSync(cacheInstalled)) return cacheDir
</file context>
Fix with Cubic

const cacheInstalled = join(cacheDir, "node_modules", PACKAGE_NAME, "package.json")
if (existsSync(cacheInstalled)) return cacheDir

const xdgCacheHome = process.env.XDG_CACHE_HOME
if (xdgCacheHome) {
Comment on lines +55 to +56

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check XDG default cache path even when env var is unset

This fallback only inspects an XDG cache location when XDG_CACHE_HOME is explicitly set, so on systems where OpenCode data is still under the default XDG path (~/.cache/opencode) the doctor check can miss an existing install and report loadedVersion as unknown. That is a regression from the previous resolver, which always considered the default XDG path via getOpenCodeCacheDir(), and it causes update/reinstall guidance to point at the wrong directory for affected users.

Useful? React with 👍 / 👎.

Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: On macOS, when XDG_CACHE_HOME is not explicitly set, the default XDG cache path (~/.cache/opencode) is never checked. getPlatformDefaultCacheDir() returns ~/Library/Caches on macOS, and the XDG branch at line 56 is only entered when the env var is explicitly set. This means macOS users who have the plugin installed at ~/.cache/opencode (the XDG default) will get unknown version. Consider also checking join(homedir(), '.cache', 'opencode') as a fallback when XDG_CACHE_HOME is unset, to cover the default XDG path on non-Linux platforms.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/doctor/checks/system-loaded-version.ts, line 56:

<comment>On macOS, when `XDG_CACHE_HOME` is not explicitly set, the default XDG cache path (`~/.cache/opencode`) is never checked. `getPlatformDefaultCacheDir()` returns `~/Library/Caches` on macOS, and the XDG branch at line 56 is only entered when the env var is explicitly set. This means macOS users who have the plugin installed at `~/.cache/opencode` (the XDG default) will get `unknown` version. Consider also checking `join(homedir(), '.cache', 'opencode')` as a fallback when `XDG_CACHE_HOME` is unset, to cover the default XDG path on non-Linux platforms.</comment>

<file context>
@@ -53,8 +43,26 @@ function normalizeVersion(value: string | undefined): string | null {
+  if (existsSync(cacheInstalled)) return cacheDir
+
+  const xdgCacheHome = process.env.XDG_CACHE_HOME
+  if (xdgCacheHome) {
+    const xdgDir = join(xdgCacheHome, "opencode")
+    if (existsSync(join(xdgDir, "node_modules", PACKAGE_NAME, "package.json"))) return xdgDir
</file context>
Fix with Cubic

const xdgDir = join(xdgCacheHome, "opencode")
if (existsSync(join(xdgDir, "node_modules", PACKAGE_NAME, "package.json"))) return xdgDir
}

return configDir
}

export function getLoadedPluginVersion(): LoadedVersionInfo {
const cacheDir = resolveOpenCodeCacheDir()
const cacheDir = resolvePluginInstallDir()
const cachePackagePath = join(cacheDir, "package.json")
const installedPackagePath = join(cacheDir, "node_modules", PACKAGE_NAME, "package.json")

Expand Down