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
- Repack with the top-level directory preserved: cd parent && zip -r runtime.zip mytopdir
- Download the original zip from the URL pinned in the manifest instead of a re-packaged mirror
- Update runtime.json to point at an archive with the expected layout
- 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
- Always zip distributions with 'zip -r out.zip topdir' from the parent directory
- Verify layout with unzip -l before distributing or pinning a URL
- Prefer official archives over re-packaged mirrors
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
- unsafe path in archive: %s
- Failed to get conversation ID from /new: {new_data}
- unsafe path in archive: %s
- extract runtime: %w
- No user message found in messages
AI-assisted analysis of xtekky/gpt4free@973504e177 (2026-08-14).
Data as JSON: /api/errors/f2cebacb2c2affc5.
Report an issue: GitHub.