{"record":{"id":"9bc11e0110dc04a4","repo":"cli/cli","slug":"error-extracting-q-w","errorCode":null,"errorMessage":"error extracting %q: %w","messagePattern":"error extracting %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/zip/zip.go","lineNumber":36,"sourceCode":")\n\n// ExtractZip extracts the contents of a zip archive to destDir.\n// Files that would result in path traversal are silently skipped.\n// Files that would produce any other error cause the extraction to be aborted,\n// and the error is returned.\nfunc ExtractZip(zr *zip.Reader, destDir safepaths.Absolute) error {\n\tfor _, zf := range zr.File {\n\t\tfpath, err := destDir.Join(zf.Name)\n\t\tif err != nil {\n\t\t\tvar pathTraversalError safepaths.PathTraversalError\n\t\t\tif errors.As(err, &pathTraversalError) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tif err := extractZipFile(zf, fpath); err != nil {\n\t\t\treturn fmt.Errorf(\"error extracting %q: %w\", zf.Name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc extractZipFile(zf *zip.File, dest safepaths.Absolute) (extractErr error) {\n\tzm := zf.Mode()\n\tif zm.IsDir() {\n\t\textractErr = os.MkdirAll(dest.String(), dirMode)\n\t\treturn\n\t}\n\n\tvar f io.ReadCloser\n\tf, extractErr = zf.Open()\n\tif extractErr != nil {\n\t\treturn\n\t}\n\tdefer f.Close()","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/cli/cli/blob/0eeec0b92edbe70199f9768522f831d3534f41ad/internal/zip/zip.go#L18-L54","documentation":"ExtractZip iterates archive entries, resolves each entry name against destDir via safepaths (path-traversal entries are silently skipped), then calls extractZipFile. If opening the entry, creating the destination file (O_EXCL, so pre-existing files fail), MkdirAll, or io.Copy fails, the error is wrapped with the offending entry name. Note the O_EXCL flag: extracting into a dirty destination where a file already exists produces \"file already exists\".","triggerScenarios":"Extracting a zip whose target file already exists in destDir (re-run over a partially extracted directory); disk full or permission denied during copy; unreadable/corrupt zip entry data; parent directory creation blocked.","commonSituations":"Retrying a failed download-extract without cleaning the destination; extracting into a directory owned by another user; truncated archive downloads producing CRC/open errors mid-entry.","solutions":["Clean the destination directory before extracting (remove partial output), since O_EXCL fails on existing files","Check free disk space and write permissions on destDir","Verify the archive integrity (unzip -t archive.zip) and re-download if the transfer was truncated","If embedding, call ExtractZip only into a fresh empty directory you created"],"exampleFix":"// before\nos.MkdirAll(dest, 0o755)\nerr := zip.ExtractZip(zr, dest) // re-run -> error extracting \"skill/README.md\": ... file exists\n\n// after\nos.RemoveAll(dest)\nif err := os.MkdirAll(dest, 0o755); err != nil { return err }\nerr := zip.ExtractZip(zr, dest)","handlingStrategy":"validation","validationCode":"// extract only into a fresh directory; O_EXCL makes re-extraction fail\nif err := os.RemoveAll(dest); err != nil { return err }\nif err := os.MkdirAll(dest, 0o755); err != nil { return err }\nif err := zip.ExtractZip(zr, safepaths.MustNewAbsolute(dest)); err != nil { return err }","typeGuard":null,"tryCatchPattern":"err := zip.ExtractZip(zr, dest)\nif err != nil {\n\tvar target safepaths.Absolute // best-effort cleanup so retry can succeed\n\t_ = os.RemoveAll(dest.String())\n\treturn err\n}","preventionTips":["Treat extraction as non-idempotent: always start from an empty destination","Verify archives (entry count/CRC) after download and before extraction","Check disk space and destination permissions before extracting"],"tags":["zip","extraction","filesystem","idempotency"],"backgroundTag":null,"analyzedSha":"0eeec0b92edbe70199f9768522f831d3534f41ad","analyzedAt":"2026-08-15T12:31:05.478Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}