jackwener/OpenCLI · warning

Failed to link host opencli into plugin: ${getErrorMessage(e

Error message

Failed to link host opencli into plugin: ${getErrorMessage(err)}

What it means

To let plugins resolve the host CLI, the host root is symlinked into the plugin directory (junction on Windows, dir symlink elsewhere). If fs.symlinkSync throws — existing target, permissions, unsupported filesystem — this warning is logged and linking is abandoned without failing the overall operation.

Source

Thrown at src/plugin.ts:1425

    const hostRoot = resolveHostOpencliRoot();

    const targetLink = path.join(pluginDir, 'node_modules', '@jackwener', 'opencli');

    // Remove existing (npm-installed copy or stale symlink)
    if (fs.existsSync(targetLink)) {
      fs.rmSync(targetLink, { recursive: true, force: true });
    }

    // Ensure parent directory exists
    fs.mkdirSync(path.dirname(targetLink), { recursive: true });

    // Use 'junction' on Windows (doesn't require admin privileges),
    // 'dir' symlink on other platforms.
    const linkType = isWindows ? 'junction' : 'dir';
    fs.symlinkSync(hostRoot, targetLink, linkType);
    log.debug(`Linked host opencli into plugin: ${targetLink} → ${hostRoot}`);
  } catch (err) {
    log.warn(`Failed to link host opencli into plugin: ${getErrorMessage(err)}`);
  }
}

/**
 * Resolve the path to the esbuild CLI executable with fallback strategies.
 */
export function resolveEsbuildBin(): string | null {
  const hostRoot = resolveHostOpencliRoot();

  // Strategy 1 (Windows): prefer the .cmd wrapper which is executable via shell
  if (isWindows) {
    const cmdPath = path.join(hostRoot, 'node_modules', '.bin', 'esbuild.cmd');
    if (fs.existsSync(cmdPath)) {
      return cmdPath;
    }
  }

  // Strategy 2: resolve esbuild binary via import.meta.resolve

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Delete the existing link first if EEXIST: fs.rmSync(targetLink, {force:true, recursive:true}) then retry.
  2. On Windows, keep the 'junction' type (as the code does) and/or enable Developer Mode for symlink support.
  3. Move the plugin directory onto a local filesystem that supports symlinks (not FAT32/network shares).
  4. Run with write permission on the plugin directory (chown/chmod or elevated shell).

Example fix

// before
fs.symlinkSync(hostRoot, targetLink, linkType);
// after
if (fs.existsSync(targetLink)) fs.rmSync(targetLink, { force: true, recursive: true });
fs.symlinkSync(hostRoot, targetLink, linkType);
Defensive patterns

Strategy: try-catch

Validate before calling

if (fs.existsSync(targetLink)) fs.rmSync(targetLink, { force: true, recursive: true });
fs.accessSync(path.dirname(targetLink), fs.constants.W_OK); // writable parent?

Type guard

function canSymlink(parentDir: string): boolean {
  try { fs.accessSync(parentDir, fs.constants.W_OK); return true; } catch { return false; }
}

Try / catch

try {
  fs.symlinkSync(hostRoot, targetLink, isWindows ? 'junction' : 'dir');
} catch (err) {
  const code = (err as NodeJS.ErrnoException).code;
  if (code === 'EEXIST') fs.rmSync(targetLink, { force: true }) || fs.symlinkSync(hostRoot, targetLink, 'junction');
  else console.warn(`link skipped (${code}); plugin may not resolve host CLI`);
}

Prevention

When it happens

Trigger: fs.symlinkSync(hostRoot, targetLink, linkType) throws: targetLink already exists (EEXIST), the plugin directory is read-only (EACCES), the filesystem doesn't support symlinks (e.g. FAT/exFAT, some network mounts), or on Windows without the junction type when privileges are missing (EPERM).

Common situations: Re-running install without cleaning up a previous link; plugins living on a synced/network drive without symlink support; Windows developer-mode disabled blocking symlink creation; CI containers running as non-root with read-only plugin dirs.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/8af4b21db1bb9d09. Report an issue: GitHub.