xtekky/gpt4free · error

archive has no top-level directory

Error message

archive has no top-level directory

What it means

The zip extractor in g4f-go/process.go scans entries to find the archive's single top-level directory (first path component other than '.'). If every entry cleans to '.' — i.e. the zip has no leading directory (files at the archive root) — it errors 'archive has no top-level directory', because the subsequent stripping logic assumes a top dir to remove.

Source

Thrown at g4f-go/process.go:71

func noSignalCtx() context.Context { return context.Background() }

// extractZip unpacks a runtime archive into dest with zip-slip protection.
// The archive has a single top-level directory (e.g. "linux-x64/"); we strip it
// so the runtime lands directly in dest, matching the launcher layout.
func extractZip(r io.ReaderAt, size int64, dest string) error {
	zr, err := zip.NewReader(r, size)
	if err != nil {
		return err
	}
	var top string
	for _, f := range zr.File {
		parts := strings.Split(filepath.Clean(f.Name), string(os.PathSeparator))
		if len(parts) > 0 && parts[0] != "." && top == "" {
			top = parts[0]
		}
	}
	if top == "" || top == "." {
		return fmt.Errorf("archive has no top-level directory")
	}

	for _, f := range zr.File {
		rel := f.Name
		if top != "" {
			rel = strings.TrimPrefix(f.Name, top+"/")
			rel = strings.TrimPrefix(rel, top)
		}
		rel = strings.TrimPrefix(rel, "/")
		name := filepath.Clean(rel)
		if name == "." || name == "" {
			continue // skip the top dir itself
		}
		if name == ".." || strings.HasPrefix(name, ".."+string(os.PathSeparator)) {
			return fmt.Errorf("unsafe path in archive: %s", f.Name)
		}
		target := filepath.Join(dest, name)
		if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) && target != filepath.Clean(dest) {

View on GitHub (pinned to 973504e177)

Solutions

  1. Repack with the top-level directory preserved: cd parent && zip -r runtime.zip mytopdir
  2. Download the original zip from the URL pinned in the manifest instead of a re-packaged mirror
  3. Update runtime.json to point at an archive with the expected layout
  4. Verify layout first: unzip -l archive.zip — entries should all start with one common directory

Example fix

# before
unzip -l runtime.zip
# python.exe
# lib/x.so           -> archive has no top-level directory

# after
mkdir pyrt && mv python.exe lib pyrt/
zip -r runtime.zip pyrt
# entries now: pyrt/python.exe, pyrt/lib/x.so
Defensive patterns

Strategy: validation

Validate before calling

// confirm the zip has one common top-level directory
func zipHasTopDir(path string) (bool, error) {
    r, err := zip.OpenReader(path)
    if err != nil { return false, err }
    defer r.Close()
    for _, f := range r.File {
        p := filepath.Clean(f.Name)
        if p != "." && p != "" {
            return true, nil // found a real leading component
        }
    }
    return false, nil
}

Try / catch

if err := extractZip(dest, r, size); err != nil {
    if strings.Contains(err.Error(), "no top-level directory") {
        // repack with a top dir or fetch the original archive
    }
    return err
}

Prevention

When it happens

Trigger: Feeding extractZip a flat archive whose members are like 'python.exe', 'lib/x.so' instead of 'python-embed/python.exe'. This includes zips produced by 'zip -j' (junk paths) or repacked artifacts.

Common situations: Re-zipping a runtime distribution for portability and dropping the top folder; a mirror re-packaging the official zip; manifest URL pointing to a differently-structured zip than expected.

Related errors


AI-assisted analysis of xtekky/gpt4free@973504e177 (2026-08-14). Data as JSON: /api/errors/f2cebacb2c2affc5. Report an issue: GitHub.