fix(coding-agent/launch): handle EISDIR from realpath on drive roots
fs.realpath throws EISDIR on Windows drive roots (e.g. R:\), but canonicalProjectDir in launch/presence.ts and launch/client.ts only recovered ENOENT, aborting startup. Both now fall back to path.resolve() on EISDIR, matching the existing ENOENT handling. Fixes #5708
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed a startup crash on Windows when running from a drive root (e.g. `R:\`): `fs.realpath` throws `EISDIR` there, but `canonicalProjectDir` in `launch/presence.ts` and `launch/client.ts` only recovered `ENOENT`. It now also falls back to `path.resolve()` on `EISDIR` ([#5708](https://github.com/can1357/oh-my-pi/issues/5708) by [@ve3xone](https://github.com/ve3xone)).
|
||||
|
||||
## [17.0.1] - 2026-07-16
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
|
||||
import * as net from "node:net";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { isEexist, isEnoent, postmortem } from "@oh-my-pi/pi-utils";
|
||||
import { isEexist, isEisdir, isEnoent, postmortem } from "@oh-my-pi/pi-utils";
|
||||
import { resolveWorkerSpawnCmd, workerEnvFromParent } from "../subprocess/worker-client";
|
||||
import { daemonBrokerEndpoint, daemonRuntimeDir } from "./paths";
|
||||
import {
|
||||
@@ -49,7 +49,7 @@ async function canonicalProjectDir(projectDir: string): Promise<string> {
|
||||
try {
|
||||
return await fs.realpath(resolved);
|
||||
} catch (error) {
|
||||
if (isEnoent(error)) return resolved;
|
||||
if (isEnoent(error) || isEisdir(error)) return resolved;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { isEnoent, postmortem } from "@oh-my-pi/pi-utils";
|
||||
import { isEisdir, isEnoent, postmortem } from "@oh-my-pi/pi-utils";
|
||||
import { daemonRuntimeDir } from "./paths";
|
||||
|
||||
const CLIENTS_DIR = "clients";
|
||||
@@ -15,7 +15,7 @@ async function canonicalProjectDir(projectDir: string): Promise<string> {
|
||||
try {
|
||||
return await fs.realpath(resolved);
|
||||
} catch (error) {
|
||||
if (isEnoent(error)) return resolved;
|
||||
if (isEnoent(error) || isEisdir(error)) return resolved;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { afterEach, describe, expect, it, vi } from "bun:test";
|
||||
import type { PathLike } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { registerDaemonProjectPresence } from "../../src/launch/presence";
|
||||
|
||||
describe("daemon presence canonicalProjectDir EISDIR fallback", () => {
|
||||
const originalRealpath = fs.realpath.bind(fs);
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("falls back to the resolved path when realpath throws EISDIR", async () => {
|
||||
const projectDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-eisdir-fallback-"));
|
||||
const runtimeDir = path.join(projectDir, "runtime");
|
||||
const resolvedProjectDir = path.resolve(projectDir);
|
||||
let realpathCalls = 0;
|
||||
|
||||
vi.spyOn(fs, "realpath").mockImplementation((async (p: PathLike) => {
|
||||
if (path.resolve(String(p)) === resolvedProjectDir) {
|
||||
realpathCalls++;
|
||||
const err = new Error("EISDIR: illegal operation on a directory") as NodeJS.ErrnoException;
|
||||
err.code = "EISDIR";
|
||||
err.errno = -21;
|
||||
err.syscall = "lstat";
|
||||
err.path = `R:${path.sep}`;
|
||||
throw err;
|
||||
}
|
||||
return originalRealpath(p);
|
||||
}) as typeof fs.realpath);
|
||||
|
||||
try {
|
||||
const presence = await registerDaemonProjectPresence(projectDir, runtimeDir);
|
||||
expect(typeof presence.close).toBe("function");
|
||||
expect(realpathCalls).toBe(1);
|
||||
|
||||
const clientsDir = path.join(runtimeDir, "clients");
|
||||
const entries = await fs.readdir(clientsDir);
|
||||
expect(entries).toHaveLength(1);
|
||||
|
||||
await presence.close();
|
||||
} finally {
|
||||
await fs.rm(projectDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user