Skip to content

Commit 717f382

Browse files
ByteYuejackwener
authored andcommitted
fix(plugin): handle EXDEV cross-filesystem rename during install
fs.renameSync() fails with EXDEV when source and destination are on different filesystem mount points. This commonly happens because plugin clones land in os.tmpdir() (often /tmp on a tmpfs) while plugins are installed to ~/.opencli/plugins/ (on the root filesystem). Add a moveDir() helper that catches EXDEV and falls back to fs.cpSync() + fs.rmSync(). Applied to both single-plugin and monorepo install paths.
1 parent 39eec0d commit 717f382

1 file changed

Lines changed: 22 additions & 2 deletions

File tree

src/plugin.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,26 @@ function resolveStoredPluginSource(lockEntry: LockEntry | undefined, pluginDir:
8888
return lockEntry?.source ?? getPluginSource(pluginDir);
8989
}
9090

91+
// ── Filesystem helpers ──────────────────────────────────────────────────────
92+
93+
/**
94+
* Move a directory, with EXDEV fallback.
95+
* fs.renameSync fails when source and destination are on different
96+
* filesystems (e.g. /tmp → ~/.opencli). In that case we copy then remove.
97+
*/
98+
function moveDir(src: string, dest: string): void {
99+
try {
100+
fs.renameSync(src, dest);
101+
} catch (err: unknown) {
102+
if ((err as NodeJS.ErrnoException).code === 'EXDEV') {
103+
fs.cpSync(src, dest, { recursive: true });
104+
fs.rmSync(src, { recursive: true, force: true });
105+
} else {
106+
throw err;
107+
}
108+
}
109+
}
110+
91111
// ── Validation helpers ──────────────────────────────────────────────────────
92112

93113
export interface ValidationResult {
@@ -292,7 +312,7 @@ function installSinglePlugin(
292312
}
293313

294314
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
295-
fs.renameSync(cloneDir, targetDir);
315+
moveDir(cloneDir, targetDir);
296316

297317
postInstallLifecycle(targetDir);
298318

@@ -404,7 +424,7 @@ function installMonorepo(
404424
// Move clone to permanent monorepos location (if not already there)
405425
if (!fs.existsSync(repoDir)) {
406426
fs.mkdirSync(monoreposDir, { recursive: true });
407-
fs.renameSync(cloneDir, repoDir);
427+
moveDir(cloneDir, repoDir);
408428
}
409429

410430
let pluginsToInstall = getEnabledPlugins(manifest);

0 commit comments

Comments
 (0)