hasura/graphql-engine · error

can't create directory tree: %w

Error message

can't create directory tree: %w

What it means

While extracting a plugin ZIP, os.MkdirAll failed for a directory entry inside the archive under the target dir. This is a filesystem-level failure: permissions, an existing non-directory file at the path, a read-only target, or disk issues, wrapped under op 'download.extractZIP'.

Source

Thrown at cli/plugins/download/downloader.go:76

func extractZIP(targetDir, fileName string, read io.ReaderAt, size int64) error {
	var op errors.Op = "download.extractZIP"

	zipReader, err := zip.NewReader(read, size)
	if err != nil {
		return errors.E(op, err)
	}

	for _, f := range zipReader.File {
		err := suspiciousPath(f.Name)
		if err != nil {
			return errors.E(op, err)
		}

		path := filepath.Join(targetDir, filepath.FromSlash(f.Name))
		if f.FileInfo().IsDir() {
			err := os.MkdirAll(path, f.Mode())
			if err != nil {
				return errors.E(op, fmt.Errorf("can't create directory tree: %w", err))
			}

			continue
		}

		src, err := f.Open()
		if err != nil {
			return errors.E(op, fmt.Errorf("could not open inflating zip file: %w", err))
		}

		dst, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode())
		if err != nil {
			_ = src.Close()

			return errors.E(op, fmt.Errorf("can't create file in zip destination dir: %w", err))
		}

		defer func() {

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Check write permissions on targetDir: ls -ld and chmod/chown as needed, or run with appropriate privileges.
  2. Clean out a partially-extracted plugin directory before re-extracting (remove stale files that conflict with archive directory entries).
  3. Verify the target filesystem is writable and not full (df, mount options).
  4. Shorten the plugin install path or enable long-path support on Windows if path length is the issue.

Example fix

// before
os.MkdirAll(path, f.Mode()) // fails: stale file blocks dir

// after
if err := os.RemoveAll(targetDir); err != nil { return err }
os.MkdirAll(path, f.Mode())
Defensive patterns

Strategy: validation

Validate before calling

// Ensure target dir is writable and clear conflicting paths before extract
if info, err := os.Stat(targetDir); err == nil && !info.IsDir() {
    return fmt.Errorf("target %s exists and is not a dir", targetDir)
}
if err := os.MkdirAll(targetDir, 0o755); err != nil { return err }
if w, err := fileutil.CanWrite(targetDir); !w { return err }

Try / catch

if err := downloader.Get(...); err != nil && strings.Contains(err.Error(), "can't create directory tree") {
    os.RemoveAll(targetDir) // clear stale conflicts, retry once
}

Prevention

When it happens

Trigger: Calling extractZIP when the process lacks write permission on targetDir, when a regular file already exists where the archive expects a directory, when the path is too long, or when the filesystem is full/read-only.

Common situations: Plugin dir owned by root while the CLI runs as a non-root user; a previous partial extraction left files behind; target dir on a read-only mount (container image); path length limits on macOS/Windows.

Related errors


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/51280f7345a877e7. Report an issue: GitHub.