Skip to content

Commit 790f4c6

Browse files
committed
fix: resolve symlinks in Filesystem.resolve() to prevent duplicate Instance contexts
Filesystem.resolve() used path.resolve() which normalizes path segments but does not resolve symlinks. When opencode runs from a symlinked directory, Instance.provide() could create duplicate contexts for the same physical directory, causing Bus event isolation and a blank TUI. Move symlink resolution (realpathSync) into Filesystem.resolve() so all callers benefit. Falls back to the unresolved path if the target does not exist, matching the guard pattern in normalizePath(). Fixes #16647 Fixes #15482
1 parent d15c2ce commit 790f4c6

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

packages/opencode/src/util/filesystem.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,15 @@ export namespace Filesystem {
114114
}
115115

116116
// We cannot rely on path.resolve() here because git.exe may come from Git Bash, Cygwin, or MSYS2, so we need to translate these paths at the boundary.
117+
// Also resolves symlinks so that callers using the result as a cache key
118+
// always get the same canonical path for a given physical directory.
117119
export function resolve(p: string): string {
118-
return normalizePath(pathResolve(windowsPath(p)))
120+
const resolved = pathResolve(windowsPath(p))
121+
try {
122+
return normalizePath(realpathSync(resolved))
123+
} catch {
124+
return normalizePath(resolved)
125+
}
119126
}
120127

121128
export function windowsPath(p: string): string {

packages/opencode/test/util/filesystem.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,5 +502,23 @@ describe("filesystem", () => {
502502
const drive = tmp.path[0].toLowerCase()
503503
expect(Filesystem.resolve(`/mnt/${drive}`)).toBe(Filesystem.resolve(`${drive.toUpperCase()}:/`))
504504
})
505+
506+
test("resolves symlinked directory to canonical path", async () => {
507+
if (process.platform === "win32") return
508+
await using tmp = await tmpdir()
509+
const link = tmp.path + "-symlink"
510+
await fs.symlink(tmp.path, link)
511+
try {
512+
expect(Filesystem.resolve(link)).toBe(Filesystem.resolve(tmp.path))
513+
} finally {
514+
await fs.unlink(link).catch(() => {})
515+
}
516+
})
517+
518+
test("returns unresolved path when target does not exist", () => {
519+
const nonExistent = "/tmp/opencode-test-does-not-exist-" + Date.now()
520+
const result = Filesystem.resolve(nonExistent)
521+
expect(result).toBe(nonExistent)
522+
})
505523
})
506524
})

0 commit comments

Comments
 (0)