{"record":{"id":"3bfddc4234a918c3","repo":"argoproj/argo-workflows","slug":"io-copy-w","errorCode":null,"errorMessage":"io copy: %w","messagePattern":"io copy: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/artifacts/gcs/gcs.go","lineNumber":186,"sourceCode":"\t\tif errors.Is(err, storage.ErrObjectNotExist) {\n\t\t\treturn argoerrors.New(argoerrors.CodeNotFound, err.Error())\n\t\t}\n\t\treturn fmt.Errorf(\"new bucket reader: %w\", err)\n\t}\n\tdefer rc.Close()\n\tout, err := os.Create(localPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"os create %s: %w\", localPath, err)\n\t}\n\tdefer func() {\n\t\tif closeErr := out.Close(); closeErr != nil {\n\t\t\tlogger := logging.RequireLoggerFromContext(ctx)\n\t\t\tlogger.WithField(\"path\", localPath).WithError(closeErr).Error(ctx, \"Error closing file\")\n\t\t}\n\t}()\n\t_, err = io.Copy(out, rc)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"io copy: %w\", err)\n\t}\n\treturn nil\n}\n\n// list all the object names of the prefix in the bucket\nfunc listByPrefix(ctx context.Context, client *storage.Client, bucket, prefix, delim string) ([]string, error) {\n\tctx, cancel := context.WithTimeout(ctx, time.Second*30)\n\tdefer cancel()\n\tit := client.Bucket(bucket).Objects(ctx, &storage.Query{\n\t\tPrefix:    prefix,\n\t\tDelimiter: delim,\n\t})\n\tresults := []string{}\n\tfor {\n\t\tattrs, err := it.Next()\n\t\tif errors.Is(err, iterator.Done) {\n\t\t\tbreak\n\t\t}","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/artifacts/gcs/gcs.go#L168-L204","documentation":"downloadObject streams the GCS object to the local file with io.Copy(out, rc). This error wraps any mid-transfer failure: the connection to GCS dropped, the context was cancelled, or reading the object body failed after the reader was opened.","triggerScenarios":"io.Copy fails during artifact download: network interruption to storage.googleapis.com, context deadline exceeded on large objects, GCS returning a resumable-read error, or disk filling up during the copy (write error surfaced through the copy).","commonSituations":"Large multi-GB artifacts over unstable networks; executor pod killed/OOM during copy; NetworkPolicy or NAT idle timeouts killing long-lived connections; exceeding ephemeral-storage limits mid-write.","solutions":["Retry the workflow or add retryStrategy to the step for transient network failures","Check/increase ephemeral-storage requests/limits so the download isn't killed","Investigate node networking (NetworkPolicy, Private Google Access, NAT timeouts) if it recurs","Use archive tar chunks or smaller artifacts to reduce single-transfer size"],"exampleFix":"// before\nsteps:\n- - name: download\n    template: get-artifact   # flaky, no retry\n// after\n- - name: download\n    template: get-artifact\n    retryStrategy: {limit: 3, retryPolicy: \"TransientError\"}","handlingStrategy":"retry","validationCode":"// pre-flight connectivity check from the pod network:\n// kubectl run net-test --image=curlimages/curl --rm -it -- \\\n//   curl -sS -o /dev/null -w '%{http_code}' https://storage.googleapis.com","typeGuard":null,"tryCatchPattern":"backoff := wait.Backoff{Steps: 3, Duration: time.Second, Factor: 2}\nerr := wait.ExponentialBackoff(backoff, func() (bool, error) {\n\terr := download(ctx, art, path)\n\tif err != nil && utilerrors.IsTransientErr(ctx, err) {\n\t\treturn false, nil // retry\n\t}\n\treturn err == nil, err\n})","preventionTips":["Add retryStrategy to steps that download large artifacts","Increase ephemeral-storage requests to survive large copies","Enable Private Google Access so traffic to GCS stays on Google's network"],"tags":["network","io","gcs","artifacts"],"backgroundTag":"network-interrupt-io-copy","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}