fix(windows): resolve symlinked config paths and plugin name parsing (fixes #2271)#2707
Conversation
|
I have read the CLA Document and I hereby sign the CLA |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23a30e86f2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const versionSeparator = keyWithoutSource.lastIndexOf("@") | ||
| const keyWithoutVersion = versionSeparator > 0 ? keyWithoutSource.slice(0, versionSeparator) : keyWithoutSource |
There was a problem hiding this comment.
Stop stripping versionless local plugin paths at parent '@' dirs
derivePluginNameFromKey() now removes everything after the last @ before checking whether the key is a file:// URL or filesystem path. For versionless local entries like file:///tmp/@work/oh-my-opencode or /tmp/@work/oh-my-opencode, that truncates the path at the parent directory, so discovery derives the wrong plugin name. Because discoverInstalledPlugins() feeds plugin.name into the namespacing logic in command-loader.ts, agent-loader.ts, and skill-loader.ts, those plugins expose the wrong plugin:command/plugin:agent/plugin:skill names and can collide with unrelated plugins whenever any parent directory contains @.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| function resolveConfigPath(pathValue: string): string { | ||
| const resolvedPath = resolve(pathValue) |
There was a problem hiding this comment.
Avoid host-OS resolve() for platform-specific config paths
Using resolve() here makes config discovery depend on the host OS path rules instead of the target platform branch. I verified this against the existing Windows-path test in src/shared/opencode-config-dir.test.ts: when the suite runs on Linux/macOS and mocks process.platform = 'win32', resolving C:\Users\TestUser\AppData\Roaming\... prepends the repo cwd, so getOpenCodeConfigDir({ binary: "opencode-desktop" }) returns an invalid path and the shared test now fails. Any other non-Windows code path that needs to inspect Windows-style config locations will hit the same regression.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
4 issues found across 5 files
Confidence score: 2/5
- There is a concrete compatibility break in
src/features/claude-code-plugin-loader/discovery.ts: scoped npm package names like@scope/pkgcan be truncated topkg, which can cause incorrect plugin discovery/naming for Opencode users. - The same parser logic in
src/features/claude-code-plugin-loader/discovery.tsalso strips at the last@, so local paths containing@may be misparsed and produce wrong plugin keys—this is a user-facing regression risk. - Additional medium-risk correctness issues in
src/shared/opencode-config-dir.tsandsrc/cli/doctor/checks/system-loaded-version.test.tssuggest path handling/canonicalization edge cases (especially cross-platform and symlinked/macOS scenarios), so merge risk is elevated rather than minimal. - Pay close attention to
src/features/claude-code-plugin-loader/discovery.ts,src/shared/opencode-config-dir.ts,src/cli/doctor/checks/system-loaded-version.test.ts- plugin key parsing and path normalization behavior can diverge in real environments.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/features/claude-code-plugin-loader/discovery.ts">
<violation number="1" location="src/features/claude-code-plugin-loader/discovery.ts:85">
P2: Plugin key parsing strips at the last '@' before path normalization, which can truncate local file paths containing '@' and derive incorrect plugin names.</violation>
<violation number="2" location="src/features/claude-code-plugin-loader/discovery.ts:95">
P1: Custom agent: **Opencode Compatibility**
Scoped npm packages (e.g., "@scope/pkg") contain a forward slash and will be incorrectly truncated to just "pkg". This breaks Opencode compatibility, which explicitly expects the plugin name to remain "@scope/pkg" for scoped packages.</violation>
</file>
<file name="src/shared/opencode-config-dir.ts">
<violation number="1" location="src/shared/opencode-config-dir.ts:46">
P2: `resolve(pathValue)` uses host-OS path semantics, not the target platform's. When running on Linux/macOS with `process.platform` mocked as `'win32'`, `getTauriConfigDir` returns a Windows-style path (e.g. `C:\Users\...`), but `resolve()` treats it as relative (not a Unix absolute path) and prepends CWD, producing an invalid path. This breaks cross-platform tests such as the existing Windows-path test in `opencode-config-dir.test.ts`.
Consider skipping `resolve()` when the path is already absolute on its target platform, or guard the `resolve` call so it only normalizes paths that match the host OS conventions.</violation>
</file>
<file name="src/cli/doctor/checks/system-loaded-version.test.ts">
<violation number="1" location="src/cli/doctor/checks/system-loaded-version.test.ts:108">
P2: Existing tests still assert raw directory strings even though `getLoadedPluginVersion()` now returns canonicalized real paths, which can fail on macOS/symlinked environments.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| } | ||
|
|
||
| function resolveConfigPath(pathValue: string): string { | ||
| const resolvedPath = resolve(pathValue) |
There was a problem hiding this comment.
P2: resolve(pathValue) uses host-OS path semantics, not the target platform's. When running on Linux/macOS with process.platform mocked as 'win32', getTauriConfigDir returns a Windows-style path (e.g. C:\Users\...), but resolve() treats it as relative (not a Unix absolute path) and prepends CWD, producing an invalid path. This breaks cross-platform tests such as the existing Windows-path test in opencode-config-dir.test.ts.
Consider skipping resolve() when the path is already absolute on its target platform, or guard the resolve call so it only normalizes paths that match the host OS conventions.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/shared/opencode-config-dir.ts, line 46:
<comment>`resolve(pathValue)` uses host-OS path semantics, not the target platform's. When running on Linux/macOS with `process.platform` mocked as `'win32'`, `getTauriConfigDir` returns a Windows-style path (e.g. `C:\Users\...`), but `resolve()` treats it as relative (not a Unix absolute path) and prepends CWD, producing an invalid path. This breaks cross-platform tests such as the existing Windows-path test in `opencode-config-dir.test.ts`.
Consider skipping `resolve()` when the path is already absolute on its target platform, or guard the `resolve` call so it only normalizes paths that match the host OS conventions.</comment>
<file context>
@@ -42,14 +42,25 @@ function getTauriConfigDir(identifier: string): string {
}
+function resolveConfigPath(pathValue: string): string {
+ const resolvedPath = resolve(pathValue)
+ if (!existsSync(resolvedPath)) return resolvedPath
+
</file context>
| @@ -1,5 +1,5 @@ | |||
| import { afterEach, describe, expect, it } from "bun:test" | |||
There was a problem hiding this comment.
P2: Existing tests still assert raw directory strings even though getLoadedPluginVersion() now returns canonicalized real paths, which can fail on macOS/symlinked environments.
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.test.ts, line 108:
<comment>Existing tests still assert raw directory strings even though `getLoadedPluginVersion()` now returns canonicalized real paths, which can fail on macOS/symlinked environments.</comment>
<file context>
@@ -104,6 +104,31 @@ describe("system loaded version", () => {
expect(loadedVersion.loadedVersion).toBe("2.3.4")
})
+
+ it("resolves symlinked config directories before selecting install path", () => {
+ //#given
+ const realConfigDir = createTemporaryDirectory("omo-real-config-")
</file context>
|
Re: issues identified by cubic. P1 (scoped npm packages truncated): Acknowledged. The plugin name parsing strips at the last P2 (local paths with @): Related to the above — paths containing P2 (cross-platform resolve): P2 (test assertions with canonicalized paths): Acknowledged as a test maintenance issue on macOS with symlinked Note: The CI test failure on this PR appears to be from the known flaky bun binary download issue, not from our changes. |
…arsing Scoped packages like @scope/pkg were truncated to just 'pkg' because basename() strips the scope prefix. Fix: - Detect scoped packages (starting with @) and find version separator after the scope slash, not at the leading @ - Return full scoped name (@scope/pkg) instead of calling basename - Add regression test for scoped package name preservation
|
Fixed P1 from cubic review: scoped npm package names are no longer truncated. Root cause: Fix:
Added regression test: |
Summary
realpathSyncbefore deriving config file and install locationsfile://...@latestentries derive the package basename (oh-my-opencode) instead of partial path segmentsProblem
On Windows setups where
~/.configis symlinked or junctioned, path handling could mix symlink aliases and canonical paths. That broke version/source detection and caused plugin name derivation to fall back to path fragments for file-based entries.Fix
Canonicalize config paths before they are used by config discovery and doctor version checks. Also parse plugin keys by stripping source/version suffixes safely and extracting basename from file URLs/paths.
Changes
src/shared/opencode-config-dir.tssrc/cli/doctor/checks/system-loaded-version.tssrc/cli/doctor/checks/system-loaded-version.test.tsOPENCODE_CONFIG_DIRsrc/features/claude-code-plugin-loader/discovery.tssrc/features/claude-code-plugin-loader/discovery.test.tsVerification
lsp_diagnostics: clean for all changed filesbun test src/features/claude-code-plugin-loader/discovery.test.ts: passbun test src/cli/doctor/checks/system-loaded-version.test.ts: fails in this environment due unresolvedpicocolorsdependency import insrc/cli/doctor/constants.tsbun run build: fails in this environment due broader package resolution issues (zod/js-yamlnot resolved by Bun loader despite install)Fixes #2271
Summary by cubic
Fix Windows handling of symlinked config/cache dirs by resolving canonical paths across config discovery, plugin detection, and doctor checks. Also fix plugin key parsing to preserve scoped npm names and derive correct names from file URLs (e.g.,
@myorg/my-plugin,oh-my-opencode). Fixes #2271.package.json, selecting install paths, or detecting installed versions (CLI, Tauri, doctor).Written for commit d7a1945. Summary will update on new commits.