rust-lang/rust · error · RuntimeError

extracted directory contains more than one dir

Error message

extracted directory contains more than one dir

What it means

Raised by cli_install() in android-sdk-manager.py at line 190 after unzipping a downloaded package into a temporary directory. The code lists the top-level entries (excluding dotfiles) of the extracted directory and expects exactly one subdirectory (the package's internal prefix). If there are zero or more than one, the package layout is unexpected and the script cannot determine which directory to move to the destination.

Source

Thrown at src/ci/docker/scripts/android-sdk-manager.py:190

            + MIRROR_BASE_DIR
        )
        downloaded = package.download(url)
        # Extract the file in a temporary directory
        extract_dir = tempfile.mkdtemp()
        subprocess.run(
            [
                "unzip",
                "-q",
                downloaded,
                "-d",
                extract_dir,
            ],
            check=True,
        )
        # Figure out the prefix used in the zip
        subdirs = [d for d in os.listdir(extract_dir) if not d.startswith(".")]
        if len(subdirs) != 1:
            raise RuntimeError("extracted directory contains more than one dir")
        # Move the extracted files in the proper directory
        dest = os.path.join(args.dest, package.path.replace(";", "/"))
        os.makedirs("/".join(dest.split("/")[:-1]), exist_ok=True)
        os.rename(os.path.join(extract_dir, subdirs[0]), dest)
        os.unlink(downloaded)


def cli():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()

    add_to_lockfile = subparsers.add_parser("add-to-lockfile")
    add_to_lockfile.add_argument("lockfile")
    add_to_lockfile.add_argument("packages", nargs="+")
    add_to_lockfile.set_defaults(func=cli_add_to_lockfile)

    update_mirror = subparsers.add_parser("update-mirror")
    update_mirror.add_argument("lockfile")

View on GitHub (pinned to 7088e4b63a)

Solutions

  1. Manually unzip the downloaded package and inspect its top-level structure to understand the anomaly.
  2. Re-download the package and re-run install (the mirror may have served a wrong/corrupt file — also check error index 16 for hash mismatch).
  3. If the package legitimately has a different layout, update the install logic to handle the correct prefix.
  4. Verify the package was not extracted into a non-empty temp directory (tempfile.mkdtemp should always be clean, but verify).
Defensive patterns

Strategy: validation

Validate before calling

# Before moving extracted files, validate the zip structure
import os
def validate_single_subdir(extract_dir):
    subdirs = [d for d in os.listdir(extract_dir) if not d.startswith('.')]
    if len(subdirs) != 1:
        print(f'Expected 1 top-level dir, found {len(subdirs)}: {subdirs}')
        return None
    return subdirs[0]

Try / catch

try:
    cli_install(args)
except RuntimeError as e:
    if 'more than one dir' in str(e):
        print('Package zip has unexpected layout. Re-download or inspect manually.')
    raise

Prevention

When it happens

Trigger: cli_install() unzips at line 177-186, then at line 188 computes subdirs = [d for d in os.listdir(extract_dir) if not d.startswith('.')]. At line 189, if len(subdirs) != 1, RuntimeError is raised at line 190. This fires when the zip archive contains multiple top-level directories or none.

Common situations: A corrupted or tampered package zip with an unexpected layout; the package format changed and now includes multiple top-level dirs; the zip was re-packaged incorrectly during mirroring; or a zip-bomb/extracted-with-extra-files scenario.

Related errors


AI-assisted analysis of rust-lang/rust@7088e4b63a (2026-08-10). Data as JSON: /api/errors/5bf678481cf16393. Report an issue: GitHub.