ruvnet/ruflo · error · ExtractionError

Expand-Archive failed

Error message

Expand-Archive failed (exit ${result.exitCode}): ${result.stderr || result.stdout}

What it means

On Windows, .zip extraction uses PowerShell's Expand-Archive via SafeExecutor (allowlist powershell/powershell.exe, 60s timeout, paths single-quote-escaped). A non-zero exit throws ExtractionError with the code and PowerShell's stderr. Expand-Archive is a script module (Microsoft.PowerShell.Archive) resolved through module autoloading, so it fails when PowerShell's execution policy blocks scripts, the module is missing/damaged, or powershell.exe isn't reachable. In the normal flow this error is caught and bsdtar 'xf' is tried as fallback — you see this message alone only when extraction is invoked directly or you're tracing the primary failure embedded in the both-extractors error.

Solutions

  1. Read the embedded stderr — 'running scripts is disabled' points at execution policy.
  2. Allow the archive module: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, or fix PSModulePath so Microsoft.PowerShell.Archive autoloads.
  3. Ensure the automatic fallback can work: keep Windows 10+ bsdtar (tar.exe) available — the installer already retries with `tar xf` when Expand-Archive fails, so a healthy tar makes this error invisible.
  4. Free disk/AV locks on the extract directory, then re-run install.

Example fix

# before: policy blocks the script module -> exit non-zero
PS> claude-flow install   # Expand-Archive failed ...

# after
PS> Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
PS> claude-flow install   # Expand-Archive (or tar fallback) succeeds
Defensive patterns

Strategy: fallback

Validate before calling

import { execFileSync } from 'node:child_process';
function expandArchiveWorks(): boolean {
  try {
    execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-Command',
      'Get-Command Expand-Archive | Out-Null'], { timeout: 15_000, stdio: 'ignore' });
    return true;
  } catch { return false; }
}
// On Windows, before relying on zip extraction:
if (!expandArchiveWorks() && !tarAvailable()) throw new Error('no working zip extractor: fix execution policy or provide tar.exe');

Type guard

function isPowerShellExtractError(e: unknown): e is Error {
  return e instanceof Error && e.message.startsWith('Expand-Archive failed (exit ');
}

Try / catch

try {
  await extractZip(archivePath, dir); // primary: Expand-Archive
} catch (primary) {
  if (!isPowerShellExtractError(primary)) throw primary;
  try {
    await extractWithTar(archivePath, dir, 'xf'); // fallback: bsdtar sniffs zip format
  } catch (fallback) {
    throw new Error(`zip extraction exhausted: Expand-Archive: ${primary.message} — tar: ${fallback.message}`);
  }
}

Prevention

When it happens

Trigger: powershell -NoProfile -NonInteractive -Command "Expand-Archive ..." exits non-zero: ExecutionPolicy Restricted blocking the Archive script module, PSModulePath broken so Microsoft.PowerShell.Archive can't autoload, PowerShell 7-only hosts where powershell.exe (5.1) is stripped, corrupted zip, or disk-full/locked destination.

Common situations: Hardened Windows images with Restricted execution policy; systems where PSModulePath was overwritten by other tooling; enterprises removing Windows PowerShell 5.1 in favor of pwsh (the allowlist names powershell/powershell.exe only); AV holding locks on extracted files.

Related errors


AI-assisted analysis of ruvnet/ruflo@29f048fc3b (2026-08-18). Data as JSON: /api/errors/7bcae8e663cc1fb0. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/proxy/install.ts:53

  if (result.exitCode !== 0) {
    throw new ExtractionError(`tar extraction failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
  }
}

/**
 * Single-quoted literal paths (doubling any embedded single quote per
 * PowerShell string-literal escaping) passed as ONE argv element to
 * `-Command`. shell:false means no OS shell ever tokenizes this string —
 * only powershell.exe's own parser does.
 */
async function extractWithPowerShell(archivePath: string, extractDir: string): Promise<void> {
  const { SafeExecutor } = await import('@claude-flow/security');
  const escape = (p: string) => p.replace(/'/g, "''");
  const command = `Expand-Archive -LiteralPath '${escape(archivePath)}' -DestinationPath '${escape(extractDir)}' -Force`;
  const exec = new SafeExecutor({ allowedCommands: ['powershell', 'powershell.exe'], timeout: 60_000 });
  const result = await exec.execute('powershell', ['-NoProfile', '-NonInteractive', '-Command', command]);
  if (result.exitCode !== 0) {
    throw new ExtractionError(`Expand-Archive failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
  }
}

/**
 * Extracts the archive via the OS's own tools — `tar` for `.tar.gz`
 * (present on macOS/Linux/Windows 10+), PowerShell `Expand-Archive`
 * specifically for `.zip` on Windows. Zero new archive-parsing dependency,
 * matching this repo's existing taste for shelling out over adding a parser dep.
 *
 * `Expand-Archive` stays the PRIMARY `.zip` path — bsdtar's zip support is
 * still not something to lean on by default. But `Microsoft.PowerShell.Archive`
 * is a *script* module resolved through PowerShell's module autoloading, so it
 * can fail for environment reasons entirely unrelated to the archive. Observed
 * in the wild on a healthy Windows 11 box, from ruflo's own `-NonInteractive`
 * child process:
 *
 *   Expand-Archive : The 'Expand-Archive' command was found in the module
 *   'Microsoft.PowerShell.Archive', but the module could not be loaded.

View on GitHub (pinned to 29f048fc3b)