{"record":{"id":"51280f7345a877e7","repo":"hasura/graphql-engine","slug":"can-t-create-directory-tree-w","errorCode":null,"errorMessage":"can't create directory tree: %w","messagePattern":"can't create directory tree: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/plugins/download/downloader.go","lineNumber":76,"sourceCode":"func extractZIP(targetDir, fileName string, read io.ReaderAt, size int64) error {\n\tvar op errors.Op = \"download.extractZIP\"\n\n\tzipReader, err := zip.NewReader(read, size)\n\tif err != nil {\n\t\treturn errors.E(op, err)\n\t}\n\n\tfor _, f := range zipReader.File {\n\t\terr := suspiciousPath(f.Name)\n\t\tif err != nil {\n\t\t\treturn errors.E(op, err)\n\t\t}\n\n\t\tpath := filepath.Join(targetDir, filepath.FromSlash(f.Name))\n\t\tif f.FileInfo().IsDir() {\n\t\t\terr := os.MkdirAll(path, f.Mode())\n\t\t\tif err != nil {\n\t\t\t\treturn errors.E(op, fmt.Errorf(\"can't create directory tree: %w\", err))\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tsrc, err := f.Open()\n\t\tif err != nil {\n\t\t\treturn errors.E(op, fmt.Errorf(\"could not open inflating zip file: %w\", err))\n\t\t}\n\n\t\tdst, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode())\n\t\tif err != nil {\n\t\t\t_ = src.Close()\n\n\t\t\treturn errors.E(op, fmt.Errorf(\"can't create file in zip destination dir: %w\", err))\n\t\t}\n\n\t\tdefer func() {","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/hasura/graphql-engine/blob/724551b9ae87845594ef0408cff0e50eb6c90dc5/cli/plugins/download/downloader.go#L58-L94","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check write permissions on targetDir: ls -ld and chmod/chown as needed, or run with appropriate privileges.","Clean out a partially-extracted plugin directory before re-extracting (remove stale files that conflict with archive directory entries).","Verify the target filesystem is writable and not full (df, mount options).","Shorten the plugin install path or enable long-path support on Windows if path length is the issue."],"exampleFix":"// before\nos.MkdirAll(path, f.Mode()) // fails: stale file blocks dir\n\n// after\nif err := os.RemoveAll(targetDir); err != nil { return err }\nos.MkdirAll(path, f.Mode())","handlingStrategy":"validation","validationCode":"// Ensure target dir is writable and clear conflicting paths before extract\nif info, err := os.Stat(targetDir); err == nil && !info.IsDir() {\n    return fmt.Errorf(\"target %s exists and is not a dir\", targetDir)\n}\nif err := os.MkdirAll(targetDir, 0o755); err != nil { return err }\nif w, err := fileutil.CanWrite(targetDir); !w { return err }","typeGuard":null,"tryCatchPattern":"if err := downloader.Get(...); err != nil && strings.Contains(err.Error(), \"can't create directory tree\") {\n    os.RemoveAll(targetDir) // clear stale conflicts, retry once\n}","preventionTips":["Extract into a fresh, per-plugin subdirectory","Clean partial extractions on failure","Run the CLI as a user owning the plugin directory"],"tags":["zip","filesystem","permissions","plugin"],"backgroundTag":"extract-permission-denied","analyzedSha":"724551b9ae87845594ef0408cff0e50eb6c90dc5","analyzedAt":"2026-08-28T07:32:55.105Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}