Files
oh-my-pi/packages/coding-agent/test/issue-845-repro.test.ts
T
can1357 a2f508faac 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
2026-04-30 04:41:36 +02:00

53 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { _resolveUpdateMethodForTest } from "../src/cli/update-cli";
// Issue #845: on Windows with Bun installed via Scoop, ~/.bun is a junction
// to scoop\persist\Oven-sh.Bun\.bun. `bun pm bin -g` and the omp path that
// $which finds may end up referring to the same directory through different
// path strings (one through the junction, one through the real target).
// `isPathInDirectory` did purely lexical comparison via path.resolve, which
// does not follow filesystem links, so it misclassified Bun-installed omp
// as "binary" and tried to swap omp.exe in place – which fails on Windows
// because Bun has the file open (EPERM on unlink of .bak).
//
// We reproduce the realpath-resolution bug with a symlink (works on macOS /
// Linux; the bug is realpath, not junction-specific).
describe("issue-845: resolveUpdateMethod follows symlinks/junctions", () => {
let tmpRoot: string;
let realBinDir: string;
let linkedBinDir: string;
let ompPathViaLink: string;
beforeAll(() => {
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "omp-issue-845-"));
realBinDir = path.join(tmpRoot, "real", "bin");
fs.mkdirSync(realBinDir, { recursive: true });
fs.writeFileSync(path.join(realBinDir, "omp"), "#!/bin/sh\n", { mode: 0o755 });
linkedBinDir = path.join(tmpRoot, "link-bin");
fs.symlinkSync(realBinDir, linkedBinDir, "dir");
ompPathViaLink = path.join(linkedBinDir, "omp");
});
afterAll(() => {
fs.rmSync(tmpRoot, { recursive: true, force: true });
});
it("classifies omp reached through a symlinked bin dir as bun-managed", () => {
// $which resolves through the symlink, `bun pm bin -g` returns the real path
// (or vice versa). Either direction must be recognized.
const method = _resolveUpdateMethodForTest(ompPathViaLink, realBinDir);
expect(method).toBe("bun");
});
it("classifies omp at the real bin dir as bun-managed when bunBinDir is symlinked", () => {
const ompAtReal = path.join(realBinDir, "omp");
const method = _resolveUpdateMethodForTest(ompAtReal, linkedBinDir);
expect(method).toBe("bun");
});
});