fix(audit): gracefully skip non-zip artifacts instead of crashing#20294
fix(audit): gracefully skip non-zip artifacts instead of crashing#20294
Conversation
…ling 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 #<issue> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR prevents the audit flow from failing entirely when gh run download encounters non-zip (e.g., .dockerbuild) artifacts by treating the specific extraction failure as non-fatal and continuing with any artifacts that were successfully downloaded.
Changes:
- Add
isNonZipArtifactErrorhelper to detect known “non-zip artifact” extraction failures fromgh run downloadoutput. - Update
downloadRunArtifactsto warn and continue on detected non-zip extraction failures, only returningErrNoArtifactsif nothing was downloaded. - Add a table-driven unit test for
isNonZipArtifactError.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| pkg/cli/logs_download.go | Adds detection + non-fatal handling for non-zip artifact extraction failures during artifact download. |
| pkg/cli/logs_download_test.go | Adds unit tests for the new detection helper. |
Comments suppressed due to low confidence (1)
pkg/cli/logs_download.go:674
- When a non-zip/extraction error is detected, the code prints a warning and then continues as if the download succeeded. Because the spinner is stopped on error and later a success message is still printed ("✓ Downloaded artifacts..."), the CLI output can be misleading. Consider tracking that this was a partial failure and adjusting the final user-facing message accordingly (e.g., "Downloaded artifacts (some skipped)") or avoid printing a success checkmark when
skippedNonZipArtifactsis true.
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))
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| // 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") |
There was a problem hiding this comment.
isNonZipArtifactError currently returns true for any output containing "error extracting zip archive". That substring can also occur for genuinely broken/partial zip downloads (e.g. unexpected EOF), not just non-zip artifacts, so this change may silently downgrade real failures to warnings and continue with incomplete data. Consider tightening the detection to only match the specific non-zip signature (e.g. "zip: not a valid zip file" / other known non-zip text), or rename the helper + warning to reflect that any extraction error is treated as non-fatal.
| return strings.Contains(s, "zip: not a valid zip file") || strings.Contains(s, "error extracting zip archive") | |
| return strings.Contains(s, "zip: not a valid zip file") |
| 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. |
There was a problem hiding this comment.
In the skippedNonZipArtifacts && IsDirEmpty(outputDir) branch, the function returns ErrNoArtifacts without attempting to download workflow run logs. The earlier "no valid artifacts" branch explicitly downloads logs so audits can still diagnose failures. For runs where all artifacts are non-zip (empty dir after gh run download), we should also call downloadWorkflowRunLogs (and optionally clean up the empty dir if that produces nothing) before returning ErrNoArtifacts.
This issue also appears on line 664 of the same file.
| // workflow logs rather than artifact content. | |
| // workflow logs rather than artifact content. | |
| // Attempt to download workflow run logs so audits can still diagnose failures, | |
| // matching the behavior of runs with no valid artifacts. | |
| if err := downloadWorkflowRunLogs(runID, outputDir, verbose, owner, repo, hostname); err != nil { | |
| // Log the error but don't fail the process; logs may be unavailable (expired/deleted). | |
| if verbose { | |
| fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to download workflow run logs: %v", err))) | |
| } | |
| } |
| { | ||
| name: "error extracting zip archive", | ||
| output: "error downloading some-artifact: error extracting zip archive: unexpected EOF", | ||
| expected: true, |
There was a problem hiding this comment.
The table case "error extracting zip archive" expects true for an "unexpected EOF" extraction failure. That broadens the behavior beyond skipping non-zip artifacts (it will also treat corrupted/partial zip downloads as non-fatal). If the intent is only to skip non-zip formats, update the test (and helper) to require a non-zip-specific signature; otherwise consider renaming the helper/test to match the broader semantics.
| expected: true, | |
| expected: false, |
The
audittool crashes completely when a workflow run contains Docker build artifacts (.dockerbuildor other non-zip formats) becausegh run downloadexits non-zero and the error was treated as fatal, producing no audit report at all.Changes
pkg/cli/logs_download.go— IndownloadRunArtifacts, detect zip-extraction failures fromgh run downloadoutput and treat them as non-fatal: emit a warning, continue processing any artifacts that were successfully downloaded, and only returnErrNoArtifactsif the output directory is still empty after the failure.Extracted the detection logic into a small helper for testability:
pkg/cli/logs_download_test.go— AddedTestIsNonZipArtifactErrorwith table-driven cases covering both detection patterns and non-matching inputs.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
https://api.github.com/graphql/usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw GO111MODULE 64/bin/go git rev-�� --show-toplevel go /usr/bin/git -json GO111MODULE 64/bin/go git(http block)/usr/bin/gh gh repo view --json owner,name --jq .owner.login + "/" + .name /usr/bin/git r.test GO111MODULE tcfg.link git rev-�� --show-toplevel Vqh7QChbBs18WVNtQl/oquH83jWyDYw0zAo1mM_/ucRcfTiI_9a60CKVjggF /usr/bin/git 3255-39667/test-git GOPROXY .a git(http block)/usr/bin/gh gh repo view owner/repo rev-�� nsion node /usr/bin/git 3255-39667/test-git lint:cjs ache/go/1.25.0/x--show-toplevel git rev-�� --show-toplevel sh /usr/bin/git "prettier" --chegit sh ache/go/1.25.0/x--show-toplevel git(http block)https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1/usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha /tmp/TestHashConsistency_GoAndJavaScript1482688897/001/test-complex-frontmatter-with-tools.md /home/REDACTED/work/gh-aw/gh-aw/pkg/cli/actionlint.go /opt/hostedtoolcache/node/24.14.0/x64/bin/node go GO111MODULE x_amd64/compile node /tmp�� /tmp/TestHashStability_SameInputSameOutput2497535679/001/stability-test.md resolved$ /usr/bin/git -json GO111MODULE 64/bin/go git(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v3/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha runs/20260310-013255-39667/test-3635491747/.github/workflows GO111MODULE ache/node/24.14.0/x64/bin/node GOINSECURE GOMOD GOMODCACHE go t-42�� sistency_GoAndJavaScript1482688897/001/test-simple-frontmatter.md GO111MODULE ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet GOINSECURE GOMOD GOMODCACHE ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v5/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go k/gh�� -json GO111MODULE 64/bin/go GOINSECURE GOMOD erignore go(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha .github/workflows/test.md go /usr/bin/git -json GO111MODULE tions/setup/node--show-toplevel git rev-�� --show-toplevel 7GIxUgZ/8d2moSUnmQW25fSdR2Uh /usr/bin/git -json GO111MODULE ache/go/1.25.0/x--show-toplevel git(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel go /usr/bin/git -json GO111MODULE ache/go/1.25.0/x--show-toplevel git rev-�� --show-toplevel go /usr/bin/git eutil.go eutil_test.go dAt,startedAt,up--show-toplevel git(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v6/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha /tmp/gh-aw-test-runs/20260310-013255-39667/test-449636421/.github/workflows config /usr/bin/infocmp remote.origin.urgit GO111MODULE 64/bin/go infocmp -1 xterm-color go /usr/bin/git -json GO111MODULE 64/bin/go git(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha k/gh-aw/gh-aw/pkg/styles/theme.go k/gh-aw/gh-aw/pkg/styles/theme_test.go /usr/bin/gh -json GO111MODULE es/.bin/sh gh api /repos/github/gh-aw/git/ref/tags/v3.0.0 --jq ache/node/24.14.0/x64/bin/node on' --ignore-patgit GO111MODULE 64/bin/go ache/node/24.14.0/x64/bin/node(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel go /usr/bin/git 1921322951/.githgit GO111MODULE 64/bin/go git rev-�� --show-toplevel go /usr/bin/git ck 'scripts/**/*git GO111MODULE x_amd64/vet git(http block)https://api.github.com/repos/actions/github-script/git/ref/tags/v8/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha GOSUMDB GOWORK 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env on' --ignore-patGOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha 0 -j ACCEPT GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/actions/setup-go/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha --show-toplevel go /tmp/go-build69306917/b413/repoutil.test -json GO111MODULE 64/bin/go /tmp/go-build69306917/b413/repoutil.test -tes�� -test.paniconexit0 -test.v=true /usr/bin/gh -test.timeout=10git -test.run=^Test -test.short=true--show-toplevel gh(http block)https://api.github.com/repos/actions/setup-node/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha /tmp/gh-aw-test-runs/20260310-013255-39667/test-449636421/.github/workflows config /usr/bin/infocmp remote.origin.urgit GO111MODULE 64/bin/go infocmp -1 xterm-color go /usr/bin/git -json GO111MODULE 64/bin/go git(http block)https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha blog-auditor.md GO111MODULE ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet GOINSECURE GOMOD GOMODCACHE ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet env -json GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/1/artifacts/usr/bin/gh gh run download 1 --dir test-logs/run-1 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/12345/artifacts/usr/bin/gh gh run download 12345 --dir test-logs/run-12345 GO111MODULE x_amd64/vet GOINSECURE GOMOD GOMODCACHE x_amd64/vet env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/12346/artifacts/usr/bin/gh gh run download 12346 --dir test-logs/run-12346 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/2/artifacts/usr/bin/gh gh run download 2 --dir test-logs/run-2 GO111MODULE x_amd64/compile GOINSECURE GOMOD dbaa609e x_amd64/compile env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/3/artifacts/usr/bin/gh gh run download 3 --dir test-logs/run-3 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/4/artifacts/usr/bin/gh gh run download 4 --dir test-logs/run-4 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/5/artifacts/usr/bin/gh gh run download 5 --dir test-logs/run-5 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/workflows/usr/bin/gh gh workflow list --json name,state,path GOSUMDB GOWORK odules/npm/node_GOMODCACHE GOINSECURE GOMOD GOMODCACHE go env json' --ignore-pGOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/contents/.github%2Fworkflows%2Faudit-workflows.md/opt/hostedtoolcache/node/24.14.0/x64/bin/node /opt/hostedtoolcache/node/24.14.0/x64/bin/node --conditions node --conditions development --experimental-import-meta-resolve --require /home/REDACTED/work/gh-aw/gh-aw/actions/setup/js/node_modules/vitest/suppress-warnings.cjs /home/REDACTED/work/gh-aw/gh-aw/actions/setup/js/node_modules/vitest/dist/workers/forks.js --stdout ode-gyp-bin/git git form�� origin/auth-cleanup-success..auth-cleanup-success --stdout t -1 --format=%s run-script/lib/node-gyp-bin/git git(http block)https://api.github.com/repos/github/gh-aw/contents/.github/workflows/shared/reporting.md/tmp/go-build69306917/b383/cli.test /tmp/go-build69306917/b383/cli.test -test.testlogfile=/tmp/go-build69306917/b383/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/contents/.github/workflows/target-workflow.md/tmp/go-build69306917/b383/cli.test /tmp/go-build69306917/b383/cli.test -test.testlogfile=/tmp/go-build69306917/b383/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/contents/.github/workflows/target-workflow.yml/tmp/go-build69306917/b383/cli.test /tmp/go-build69306917/b383/cli.test -test.testlogfile=/tmp/go-build69306917/b383/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha -json GO111MODULE datedAt,event,headBranch,headSha,displayTitle GOINSECURE GOMOD GOMODCACHE go env 1445563556/.github/workflows GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.2.3/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.2.3 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v2.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq .object.sha on' --ignore-patGOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq .object.sha on' --ignore-patGOINSECURE GO111MODULE x_amd64/compile GOINSECURE GOMOD GOMODCACHE x_amd64/compile env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v3.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v3.0.0 --jq .object.sha on' --ignore-patGOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999/usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha -json GO111MODULE x_amd64/vet GOINSECURE GOMOD GOMODCACHE 7KRlb0f/T36gUPQZXFjOiSQ6baL5 env setup/js && npm run check:pkg-jsgo1.25.0 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/nonexistent/repo/actions/runs/12345/usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE is not installed. Run 'make deps-dev' to installGO111MODULE(http block)https://api.github.com/repos/owner/repo/actions/workflows/usr/bin/gh gh workflow list --json name,state,path --repo owner/repo 64/bin/go GOINSECURE GOMOD GOMODCACHE go env json' --ignore-pGOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh workflow list --repo owner/repo --json name,path,state /usr/bin/git -json GO111MODULE ache/go/1.25.0/x--show-toplevel git rev-�� --show-toplevel node /usr/bin/git archie.md --check 64/pkg/tool/linu--show-toplevel git(http block)https://api.github.com/repos/owner/repo/contents/file.md/tmp/go-build69306917/b383/cli.test /tmp/go-build69306917/b383/cli.test -test.testlogfile=/tmp/go-build69306917/b383/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/test-owner/test-repo/actions/secrets/usr/bin/gh gh api /repos/test-owner/test-repo/actions/secrets --jq .secrets[].name GOSUMDB GOWORK ode_modules/.binGOMODCACHE GOINSECURE GOMOD GOMODCACHE go env json' --ignore-pGOINSECURE GO111MODULE odules/npm/node_GOMODCACHE GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/test/repo/usr/bin/gh gh api /repos/test/repo --jq .default_branch --show-toplevel oGMQb-a2YasnmeOEa7/uvoaxXZ6SdsIYTest User /usr/bin/git npx prettier --cgit GOPROXY .a git rev-�� --show-toplevel node /usr/bin/git --check scripts/**/*.js ache/go/1.25.0/xazure-vmextensions-Microsoft.CPlat.Core.RunCommandLinux.slice git(http block)If you need me to access, download, or install something from one of these locations, you can either:
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.