From 81a03d1b4aef708238f942974ff16664dbaa609e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 01:22:59 +0000 Subject: [PATCH 1/2] Initial plan From d4d9eaf9616eb1a1e530d71c124b616b07ad0f1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 01:39:50 +0000 Subject: [PATCH 2/2] fix(audit): skip non-zip artifacts (e.g. .dockerbuild) instead of failing When `gh run download` encounters artifacts that are not valid zip archives (such as .dockerbuild files produced by Docker builds), the audit tool no longer crashes. Instead: - Emits a concise warning identifying the skipped artifacts - Continues processing any artifacts that were successfully downloaded - Falls back to ErrNoArtifacts only when nothing at all was downloaded Adds `isNonZipArtifactError` helper and a comprehensive unit test. Fixes # Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/logs_download.go | 35 ++++++++++++++++++++++++- pkg/cli/logs_download_test.go | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/pkg/cli/logs_download.go b/pkg/cli/logs_download.go index d15297e19c2..45ea85c43a2 100644 --- a/pkg/cli/logs_download.go +++ b/pkg/cli/logs_download.go @@ -558,6 +558,15 @@ func listArtifacts(outputDir string) ([]string, error) { return artifacts, nil } +// isNonZipArtifactError reports whether the output from gh run download indicates +// that the failure was caused by one or more non-zip artifacts (e.g. .dockerbuild files). +// Such artifacts cannot be extracted as zip archives and should be skipped rather than +// failing the entire download. +func isNonZipArtifactError(output []byte) bool { + s := string(output) + return strings.Contains(s, "zip: not a valid zip file") || strings.Contains(s, "error extracting zip archive") +} + // downloadRunArtifacts downloads artifacts for a specific workflow run func downloadRunArtifacts(runID int64, outputDir string, verbose bool, owner, repo, hostname string) error { logsDownloadLog.Printf("Downloading run artifacts: run_id=%d, output_dir=%s, owner=%s, repo=%s", runID, outputDir, owner, repo) @@ -612,6 +621,10 @@ func downloadRunArtifacts(runID int64, outputDir string, verbose bool, owner, re cmd := workflow.ExecGH(ghArgs...) output, err := cmd.CombinedOutput() + // skippedNonZipArtifacts is set when gh run download fails due to non-zip artifacts + // (e.g., .dockerbuild files). In that case we warn and continue with what was downloaded. + var skippedNonZipArtifacts bool + if err != nil { // Stop spinner on error if !verbose { @@ -645,7 +658,27 @@ func downloadRunArtifacts(runID int64, outputDir string, verbose bool, owner, re if strings.Contains(err.Error(), "exit status 4") { return errors.New("GitHub CLI authentication required. Run 'gh auth login' first") } - return fmt.Errorf("failed to download artifacts for run %d: %w (output: %s)", runID, err, string(output)) + // Check if the error is due to non-zip artifacts (e.g., .dockerbuild files). + // The gh CLI fails when it encounters artifacts that are not valid zip archives. + // We warn and continue with any artifacts that were successfully downloaded. + if isNonZipArtifactError(output) { + // Show a concise warning; the raw output may be verbose so truncate it. + msg := string(output) + if len(msg) > 200 { + msg = msg[:200] + "..." + } + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Some artifacts could not be extracted (not a valid zip archive) and were skipped: "+msg)) + skippedNonZipArtifacts = true + } else { + return fmt.Errorf("failed to download artifacts for run %d: %w (output: %s)", runID, err, string(output)) + } + } + + if skippedNonZipArtifacts && fileutil.IsDirEmpty(outputDir) { + // All artifacts were non-zip (none could be extracted) so nothing was downloaded. + // Treat this the same as a run with no artifacts — the audit will rely solely on + // workflow logs rather than artifact content. + return ErrNoArtifacts } // Stop spinner with success message diff --git a/pkg/cli/logs_download_test.go b/pkg/cli/logs_download_test.go index 534e6780a29..bf5feb031e7 100644 --- a/pkg/cli/logs_download_test.go +++ b/pkg/cli/logs_download_test.go @@ -118,6 +118,54 @@ func TestErrNoArtifacts(t *testing.T) { } } +func TestIsNonZipArtifactError(t *testing.T) { + tests := []struct { + name string + output string + expected bool + }{ + { + name: "zip: not a valid zip file", + output: "error downloading github~gh-aw~39RTHX.dockerbuild: error extracting zip archive: zip: not a valid zip file", + expected: true, + }, + { + name: "error extracting zip archive", + output: "error downloading some-artifact: error extracting zip archive: unexpected EOF", + expected: true, + }, + { + name: "both patterns present", + output: "error extracting zip archive: zip: not a valid zip file", + expected: true, + }, + { + name: "unrelated error", + output: "exit status 1: some other failure", + expected: false, + }, + { + name: "empty output", + output: "", + expected: false, + }, + { + name: "no artifacts found", + output: "no valid artifacts found", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isNonZipArtifactError([]byte(tt.output)) + if result != tt.expected { + t.Errorf("isNonZipArtifactError(%q) = %v, want %v", tt.output, result, tt.expected) + } + }) + } +} + func TestListWorkflowRunsWithPagination(t *testing.T) { // Test that listWorkflowRunsWithPagination properly adds beforeDate filter // Since we can't easily mock the GitHub CLI, we'll test with known auth issues