Skip to content

Commit bb287b7

Browse files
committed
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 218ba91 commit bb287b7

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
@@ -70,6 +70,26 @@ export interface PluginInfo {
7070
description?: string;
7171
}
7272

73+
// ── Filesystem helpers ──────────────────────────────────────────────────────
74+
75+
/**
76+
* Move a directory, with EXDEV fallback.
77+
* fs.renameSync fails when source and destination are on different
78+
* filesystems (e.g. /tmp → ~/.opencli). In that case we copy then remove.
79+
*/
80+
function moveDir(src: string, dest: string): void {
81+
try {
82+
fs.renameSync(src, dest);
83+
} catch (err: unknown) {
84+
if ((err as NodeJS.ErrnoException).code === 'EXDEV') {
85+
fs.cpSync(src, dest, { recursive: true });
86+
fs.rmSync(src, { recursive: true, force: true });
87+
} else {
88+
throw err;
89+
}
90+
}
91+
}
92+
7393
// ── Validation helpers ──────────────────────────────────────────────────────
7494

7595
export interface ValidationResult {
@@ -266,7 +286,7 @@ function installSinglePlugin(
266286
}
267287

268288
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
269-
fs.renameSync(cloneDir, targetDir);
289+
moveDir(cloneDir, targetDir);
270290

271291
postInstallLifecycle(targetDir);
272292

@@ -298,7 +318,7 @@ function installMonorepo(
298318
// Move clone to permanent monorepos location (if not already there)
299319
if (!fs.existsSync(repoDir)) {
300320
fs.mkdirSync(monoreposDir, { recursive: true });
301-
fs.renameSync(cloneDir, repoDir);
321+
moveDir(cloneDir, repoDir);
302322
}
303323

304324
let pluginsToInstall = getEnabledPlugins(manifest);

0 commit comments

Comments
 (0)