ruvnet/ruflo · error · ExtractionError
archive did not contain the expected binary at its root
Error message
archive did not contain the expected binary at its root: ${binaryNameInArchive()} What it means
After extracting the meta-proxy release archive, install checks that the expected binary (meta-proxy, or meta-proxy.exe on Windows) exists at the ROOT of the extraction directory. If it does not, it throws — meaning extraction 'succeeded' but the layout isn't what the installer expects: the release artifact nested the binary in a subfolder, the platform triple selected an archive with a different layout, or the archive was empty/not actually a binary release. A subsequent PathValidator check then guards against symlink tricks, but this error is purely 'the file isn't where it must be'.
Solutions
- Inspect the actual layout: extract the release archive for your platform manually (`tar -tf <asset>`) and see where the binary sits.
- Pin a known-good version until the layout mismatch is fixed: install with the previously working version number.
- Verify the detected platform triple (OS/arch) matches the asset you'd expect — wrong triple can select a differently-laid-out archive.
- If the release asset is simply wrong (nested/empty), report it — there is no flag to relocate the binary; the archive must contain it at the root.
Example fix
# before: release asset nests the binary $ tar -tf meta-proxy-v9-x86_64-apple-darwin.tar.gz meta-proxy-9.0.0/bin/meta-proxy # NOT at root -> error 299 # after: pin the last correctly-packaged release npx ruflo@latest proxy install --version 0.4.1 # archive has ./meta-proxy at root
Defensive patterns
Strategy: validation
Validate before calling
import { execFileSync } from 'node:child_process';
// Verify the release asset actually has the binary at its root BEFORE installing:
function archiveHasRootBinary(archivePath: string, binary: string): boolean {
const listing = execFileSync('tar', ['-tf', archivePath], { encoding: 'utf-8', timeout: 60_000 });
return listing.split('\n').map(l => l.trim().replace(/^\.\//, '')).includes(binary);
}
if (!archiveHasRootBinary(assetPath, process.platform === 'win32' ? 'meta-proxy.exe' : 'meta-proxy')) {
throw new Error(`release asset layout invalid for ${version} — pin a known-good version`);
} Type guard
function isMissingBinaryError(e: unknown): e is Error {
return e instanceof Error && e.message.startsWith('archive did not contain the expected binary at its root');
} Try / catch
try {
return await installProxyBinary({ version: 'latest' });
} catch (e) {
if (isMissingBinaryError(e)) {
return installProxyBinary({ version: LAST_KNOWN_GOOD_VERSION }); // pin back to a correctly-packaged release
}
throw e;
} Prevention
- Pin proxy install to a specific known-good version in automation instead of floating latest.
- When a new release ships, verify its archive layout (`tar -tf`) for your platform before rolling it out.
- Confirm the platform triple detection (os/arch) matches your host so the expected asset layout matches the binary name.
When it happens
Trigger: install downloads the asset for the detected platform triple and runs extractArchive, but the tarball's top level is e.g. 'meta-proxy-0.4.2-x86_64/bin/meta-proxy' instead of './meta-proxy'; or a release accidentally shipped sources only; or wrong-architecture asset (arm64 vs x64) with a different internal layout; or extraction silently extracting nothing (empty archive) while exiting 0.
Common situations: Upgrading to a release whose packaging changed layout without an installer update; running on an unusual platform triple whose asset is packaged differently; a release-process regression that nested binaries; proxies/mirrors serving a generic source tarball instead of the binary asset.
Related errors
- Expand-Archive failed
- Previous Meta-Proxy .
- Rollback failed
- statusline-generator: could not locate…
- tar extraction failed
AI-assisted analysis of ruvnet/ruflo@29f048fc3b (2026-08-18).
Data as JSON: /api/errors/add42de5e916f1fe.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/proxy/install.ts:153
assetBytes: assets.archiveBytes,
assetFilename: assets.archiveFilename,
});
log(`Verified — sha256 ${sha256.slice(0, 16)}…`);
// fetchReleaseAssets's dev (gh) path already wrote the archive to workDir
// under archiveFilename; ensure it's there regardless of source so
// extraction always has a real file to operate on.
const archivePath = path.join(workDir, archiveFilename);
if (!fs.existsSync(archivePath)) {
fs.writeFileSync(archivePath, assets.archiveBytes);
}
const extractDir = path.join(workDir, 'extracted');
await extractArchive(archivePath, extractDir, releaseArchiveExtension(triple));
const extractedBinaryPath = path.join(extractDir, binaryNameInArchive());
if (!fs.existsSync(extractedBinaryPath)) {
throw new ExtractionError(`archive did not contain the expected binary at its root: ${binaryNameInArchive()}`);
}
// Defense in depth: confirm the extracted binary genuinely resolves
// inside extractDir (catches a symlink swap or similar), even though
// we only ever read one specific expected relative path, never an
// archive-listed one (so "zip slip" via arbitrary archive paths isn't
// reachable here in the first place).
const { PathValidator } = await import('@claude-flow/security');
const validator = new PathValidator({ allowedPrefixes: [extractDir] });
const validation = await validator.validate(extractedBinaryPath);
if (!validation.isValid) {
throw new ExtractionError(`extracted binary path failed validation: ${validation.errors.join('; ') || 'unknown'}`);
}
const finalPath = proxyBinaryPath();
fs.mkdirSync(path.dirname(finalPath), { recursive: true, mode: 0o700 });
const tmp = `${finalPath}.tmp`;
fs.copyFileSync(extractedBinaryPath, tmp);View on GitHub (pinned to 29f048fc3b)