golang/go · error

zip file contains more than one top-level directory

Error message

zip file contains more than one top-level directory

What it means

Companion to 1004. Once the top-level prefix is locked in from the first entry, every subsequent entry must start with that prefix. Finding an entry that does not means the archive contains more than one top-level directory, which would let a module smuggle files outside its namespace.

Source

Thrown at src/cmd/go/internal/modfetch/coderepo.go:1154

	var files []modzip.File
	if subdir != "" {
		subdir += "/"
	}
	haveLICENSE := false
	topPrefix := ""
	for _, zf := range zr.File {
		if topPrefix == "" {
			i := strings.Index(zf.Name, "/")
			if i < 0 {
				return fmt.Errorf("missing top-level directory prefix")
			}
			topPrefix = zf.Name[:i+1]
		}
		var name string
		var found bool
		if name, found = strings.CutPrefix(zf.Name, topPrefix); !found {
			return fmt.Errorf("zip file contains more than one top-level directory")
		}

		if name, found = strings.CutPrefix(name, subdir); !found {
			continue
		}

		if name == "" || strings.HasSuffix(name, "/") {
			continue
		}
		files = append(files, zipFile{name: name, f: zf})
		if name == "LICENSE" {
			haveLICENSE = true
		}
	}

	if !haveLICENSE && subdir != "" {
		data, err := r.code.ReadFile(ctx, rev, "LICENSE", codehost.MaxLICENSE)
		if err == nil {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Purge the suspect cache entry and re-download.
  2. Point GOPROXY at a compliant proxy (https://proxy.golang.org,direct) that always emits a single top-level dir.
  3. If self-hosting the proxy, validate that the upstream archive contains exactly one root directory before serving.
Defensive patterns

Strategy: validation

Validate before calling

func zipSingleTopDir(p string) error {
    r, err := zip.OpenReader(p)
    if err != nil { return err }
    defer r.Close()
    top := ""
    for _, zf := range r.File {
        i := strings.Index(zf.Name, "/")
        if i < 0 { return fmt.Errorf("no top dir") }
        if top == "" { top = zf.Name[:i+1] }
        if !strings.HasPrefix(zf.Name, top) {
            return fmt.Errorf("multiple top dirs: %q vs %q", top, zf.Name)
        }
    }
    return nil
}

Prevention

When it happens

Trigger: strings.CutPrefix(zf.Name, topPrefix) returns found=false for some entry — e.g. archive holds both projectA/... and projectB/... at the top level.

Common situations: Multi-project repos packaged into one zip by a misbehaving proxy; corrupted download; cache tampering.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/aa7ce7d0cbb01982. Report an issue: GitHub.