diff --git a/Gemfile b/Gemfile
index d472cc83ec1fb2a338b466709f8d5613e8bac921..5d2e5697e3855de9f0ca2ec9bba2558ee762c557 100644
--- a/Gemfile
+++ b/Gemfile
@@ -485,7 +485,7 @@ gem 'ssh_data', '~> 1.2'
 gem 'spamcheck', '~> 0.1.0'
 
 # Gitaly GRPC protocol definitions
-gem 'gitaly', '~> 15.0.0-rc3'
+gem 'gitaly', '~> 15.1.0-rc1'
 
 # KAS GRPC protocol definitions
 gem 'kas-grpc', '~> 0.0.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 53e8c321d22a415a037cf130a15e00fe8f9b9104..fccc223c99c25a26cea5aee1dcfd83a6d696387d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -466,7 +466,7 @@ GEM
       rails (>= 3.2.0)
     git (1.7.0)
       rchardet (~> 1.8)
-    gitaly (15.0.0.pre.rc3)
+    gitaly (15.1.0.pre.rc1)
       grpc (~> 1.0)
     github-markup (1.7.0)
     gitlab (4.16.1)
@@ -1529,7 +1529,7 @@ DEPENDENCIES
   gettext (~> 3.3)
   gettext_i18n_rails (~> 1.8.0)
   gettext_i18n_rails_js (~> 1.3)
-  gitaly (~> 15.0.0.pre.rc3)
+  gitaly (~> 15.1.0.pre.rc1)
   github-markup (~> 1.7.0)
   gitlab-chronic (~> 0.10.5)
   gitlab-dangerfiles (~> 3.0)
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 5a1699541d02ef62dcd4a3a93171d52d55b87e16..46a9329d1485b7e106dfa44196af1ad874c3f1ae 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -101,6 +101,16 @@ def user_delete_branch(branch_name, user)
         if pre_receive_error = response.pre_receive_error.presence
           raise Gitlab::Git::PreReceiveError, pre_receive_error
         end
+      rescue GRPC::BadStatus => e
+        detailed_error = decode_detailed_error(e)
+
+        case detailed_error&.error
+        when :custom_hook
+          raise Gitlab::Git::PreReceiveError.new(custom_hook_error_message(detailed_error.custom_hook),
+                                                 fallback_message: e.details)
+        else
+          raise
+        end
       end
 
       def user_merge_to_ref(user, source_sha:, branch:, target_ref:, message:, first_parent_ref:, allow_conflicts: false)
@@ -164,14 +174,8 @@ def user_merge_branch(user, source_sha, target_branch, message)
           # These messages were returned from internal/allowed API calls
           raise Gitlab::Git::PreReceiveError.new(fallback_message: access_check_error.error_message)
         when :custom_hook
-          # Custom hooks may return messages via either stdout or stderr which have a specific prefix. If
-          # that prefix is present we'll want to print the hook's output, otherwise we'll want to print the
-          # Gitaly error as a fallback.
-          custom_hook_error = detailed_error.custom_hook
-          custom_hook_output = custom_hook_error.stderr.presence || custom_hook_error.stdout
-          error_message = EncodingHelper.encode!(custom_hook_output)
-
-          raise Gitlab::Git::PreReceiveError.new(error_message, fallback_message: e.details)
+          raise Gitlab::Git::PreReceiveError.new(custom_hook_error_message(detailed_error.custom_hook),
+                                                 fallback_message: e.details)
         when :reference_update
           # We simply ignore any reference update errors which are typically an
           # indicator of multiple RPC calls trying to update the same reference
@@ -550,6 +554,14 @@ def decode_detailed_error(err)
         # Error Class might not be known to ruby yet
         nil
       end
+
+      def custom_hook_error_message(custom_hook_error)
+        # Custom hooks may return messages via either stdout or stderr which have a specific prefix. If
+        # that prefix is present we'll want to print the hook's output, otherwise we'll want to print the
+        # Gitaly error as a fallback.
+        custom_hook_output = custom_hook_error.stderr.presence || custom_hook_error.stdout
+        EncodingHelper.encode!(custom_hook_output)
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 9b6efa30ff7aa80ec0cd08ae53be64c26c0adb2f..4b88472db60e755d201049ee6c0da8a9ec8e8b5b 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -170,6 +170,65 @@
           Gitlab::Git::PreReceiveError, "something failed")
       end
     end
+
+    context 'with a custom hook error' do
+      let(:stdout) { nil }
+      let(:stderr) { nil }
+      let(:error_message) { "error_message" }
+      let(:custom_hook_error) do
+        new_detailed_error(
+          GRPC::Core::StatusCodes::PERMISSION_DENIED,
+          error_message,
+          Gitaly::UserDeleteBranchError.new(
+            custom_hook: Gitaly::CustomHookError.new(
+              stdout: stdout,
+              stderr: stderr,
+              hook_type: Gitaly::CustomHookError::HookType::HOOK_TYPE_PRERECEIVE
+            )))
+      end
+
+      shared_examples 'a failed branch deletion' do
+        it 'raises a PreRecieveError' do
+          expect_any_instance_of(Gitaly::OperationService::Stub)
+            .to receive(:user_delete_branch).with(request, kind_of(Hash))
+            .and_raise(custom_hook_error)
+
+          expect { subject }.to raise_error do |error|
+            expect(error).to be_a(Gitlab::Git::PreReceiveError)
+            expect(error.message).to eq(expected_message)
+            expect(error.raw_message).to eq(expected_raw_message)
+          end
+        end
+      end
+
+      context 'when details contain stderr' do
+        let(:stderr) { "something" }
+        let(:stdout) { "GL-HOOK-ERR: stdout is overridden by stderr" }
+        let(:expected_message) { error_message }
+        let(:expected_raw_message) { stderr }
+
+        it_behaves_like 'a failed branch deletion'
+      end
+
+      context 'when details contain stdout' do
+        let(:stderr) { "      \n" }
+        let(:stdout) { "something" }
+        let(:expected_message) { error_message }
+        let(:expected_raw_message) { stdout }
+
+        it_behaves_like 'a failed branch deletion'
+      end
+    end
+
+    context 'with a non-detailed error' do
+      it 'raises a GRPC error' do
+        expect_any_instance_of(Gitaly::OperationService::Stub)
+          .to receive(:user_delete_branch).with(request, kind_of(Hash))
+          .and_raise(GRPC::Internal.new('non-detailed error'))
+
+        expect { subject }.to raise_error(GRPC::Internal)
+      end
+    end
   end
 
   describe '#user_merge_branch' do