fix(coding-agent): resolve junctions/symlinks when classifying omp update target

isPathInDirectory only normalized strings via path.resolve, so on Windows
when Bun is installed via Scoop (~/.bun is a junction to scoop\persist\
Oven-sh.Bun\.bun) the omp path from $which and the bunBinDir from
'bun pm bin -g' compared as different directories, causing 'omp update'
to take the binary-swap path instead of 'bun install -g' and fail with
EPERM unlinking omp.exe.bak (Bun has the running exe open). Layer
fs.realpathSync.native on top of the existing lexical guard, resolving
the file's parent dir so non-existent target paths still fall through.

Fixes #845
This commit is contained in:
can1357
2026-04-30 04:25:08 +02:00
parent 2f756bcc1b
commit a2f508faac
2 changed files with 77 additions and 1 deletions
+25 -1
View File
@@ -53,13 +53,37 @@ function normalizePathForComparison(filePath: string): string {
return normalized;
}
function isPathInDirectory(filePath: string, directoryPath: string): boolean {
function tryRealpath(p: string): string | undefined {
try {
return fs.realpathSync.native(p);
} catch {
return undefined;
}
}
function isPathInDirectoryLexical(filePath: string, directoryPath: string): boolean {
const normalizedPath = normalizePathForComparison(path.resolve(filePath));
const normalizedDirectory = normalizePathForComparison(path.resolve(directoryPath));
const relativePath = path.relative(normalizedDirectory, normalizedPath);
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
}
function isPathInDirectory(filePath: string, directoryPath: string): boolean {
if (isPathInDirectoryLexical(filePath, directoryPath)) return true;
// Layer realpath resolution on top of the lexical guard. On Windows, ~/.bun
// is a junction when Bun is installed via Scoop, so `bun pm bin -g` and the
// PATH-resolved omp path can refer to the same directory through different
// strings. path.resolve does not traverse junctions/symlinks; realpath does.
// Resolve the file's parent directory to tolerate the file itself not yet
// existing (e.g. a fresh install path) while still catching link-traversed
// equality once the directory exists.
const fileDir = tryRealpath(path.dirname(path.resolve(filePath)));
const dirReal = tryRealpath(path.resolve(directoryPath));
if (!fileDir || !dirReal) return false;
const resolvedFile = path.join(fileDir, path.basename(filePath));
return isPathInDirectoryLexical(resolvedFile, dirReal);
}
type UpdateTarget = { method: "bun" } | { method: "binary"; path: string };
function resolveUpdateMethod(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {