{"record":{"id":"261776a5cd966678","repo":"GoogleContainerTools/skaffold","slug":"error-closing-gcs-writer-w","errorCode":null,"errorMessage":"error closing GCS writer: %w","messagePattern":"error closing GCS writer: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/skaffold/gcs/client/native.go","lineNumber":349,"sourceCode":"\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create file: %v\", err)\n\t}\n\tdefer file.Close()\n\n\tif _, err := io.Copy(file, reader); err != nil {\n\t\treturn fmt.Errorf(\"failed to copy object to file: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (nb nativeBucketHandler) UploadObject(ctx context.Context, objName string, content *os.File) error {\n\twc := nb.bucket.Object(objName).NewWriter(ctx)\n\tif _, err := io.Copy(wc, content); err != nil {\n\t\treturn fmt.Errorf(\"error copying file to GCS: %w\", err)\n\t}\n\tif err := wc.Close(); err != nil {\n\t\treturn fmt.Errorf(\"error closing GCS writer: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (nb nativeBucketHandler) Close() {\n\tnb.storageClient.Close()\n}\n","sourceCodeStart":331,"sourceCodeEnd":357,"githubUrl":"https://github.com/GoogleContainerTools/skaffold/blob/a1189de023efc32d4b8e11f395acc678aa555011/pkg/skaffold/gcs/client/native.go#L331-L357","documentation":"This error is returned by nativeBucketHandler.UploadObject when the GCS writer's Close() call fails after the file content has been copied. Closing a GCS writer is what actually flushes the data and finalizes the object upload; a failure here means the object was NOT successfully written despite io.Copy succeeding. It wraps the underlying error (network, permissions, or API failure) with %w for inspection via errors.Is/As.","triggerScenarios":"Calling UploadObject (native GCS client path) where io.Copy succeeds but wc.Close() fails: network interruption during final flush, insufficient IAM permissions (storage.objects.create denied), bucket constraints (e.g. CMEK key unavailable, retention policy), or context cancellation right at the end of the upload.","commonSituations":"Uploading sources/artifacts to a GCS remote cache when credentials lack write access to the bucket; transient network drops mid-upload; service account key rotation revoking access; bucket using customer-managed encryption keys whose Cloud KMS permission was removed.","solutions":["Run `gcloud storage objects create`-equivalent check: verify the identity has storage.objects.create on the target bucket via `gsutil acl get` or IAM console","Re-run the upload; Close failures from transient network issues are often resolved by retrying","Check context cancellation upstream — ensure no timeout is killing the upload mid-flight","If the bucket uses CMEK, verify the service account has roles/cloudkms.cryptoKeyEncrypterDecrypter","Verify bucket retention/soft-delete policies are not rejecting the finalize operation"],"exampleFix":"// before\nif err := wc.Close(); err != nil {\n\treturn fmt.Errorf(\"error closing GCS writer: %w\", err)\n}\n// after\nif err := wc.Close(); err != nil {\n\tif ctx.Err() != nil {\n\t\treturn fmt.Errorf(\"error closing GCS writer (context canceled): %w\", ctx.Err())\n\t}\n\treturn fmt.Errorf(\"error closing GCS writer: %w\", err)\n}","handlingStrategy":"retry","validationCode":"// validate bucket access before uploading\nimport \"cloud.google.com/go/storage\"\nbkt := client.Bucket(bucketName)\nif _, err := bkt.Attrs(ctx); err != nil {\n\treturn fmt.Errorf(\"bucket %s not accessible: %w\", bucketName, err)\n}\nif _, err := os.Stat(localFile); err != nil {\n\treturn fmt.Errorf(\"content file missing: %w\", err)\n}","typeGuard":"func isGCSError(err error) bool {\n\tvar se *googleapi.Error\n\treturn errors.As(err, &se)\n}","tryCatchPattern":"if err := handler.UploadObject(ctx, objName, f); err != nil {\n\tvar se *googleapi.Error\n\tif errors.As(err, &se) && se.Code >= 500 {\n\t\t// retry with backoff\n\t}\n\tif errors.Is(err, context.Canceled) {\n\t\t// caller canceled; don't retry\n\t}\n\treturn err\n}","preventionTips":["Grant the service account storage.objects.create on the target bucket before uploads","Avoid cancelling the context until after UploadObject returns","Retry on transient (5xx) googleapi errors with exponential backoff","For CMEK buckets, verify Cloud KMS encrypter/decrypter permissions","Use resilient writers so Close() failures surface early with partial-upload context"],"tags":["gcs","upload","io","network","go"],"backgroundTag":"cloud-storage-upload-failed","analyzedSha":"a1189de023efc32d4b8e11f395acc678aa555011","analyzedAt":"2026-09-05T12:09:27.064Z","contentChangedAt":"2026-09-05T12:09:27.064Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}