diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index b8ada6361ace5cb782c84e99f8a077671aa8ebbd..860ac16eefd692895b0d18b10f57bcae57647d6e 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -291,9 +291,7 @@ module Ci
     end
 
     def can_be_served?(runner)
-      return false unless has_tags? || runner.run_untagged?
-
-      (tag_list - runner.tag_list).empty?
+      runner.can_serve?(self)
     end
 
     def has_tags?
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index adb652922085d211e23d9588dc4ff9ae26a250d1..d61a8c00634df08a79df904454ae3b05bd120fc0 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -91,6 +91,12 @@ module Ci
       !shared?
     end
 
+    def can_serve?(build)
+      not_locked_or_locked_to?(build.project) &&
+        run_untagged_or_has_tags?(build) &&
+        accepting_tags?(build.tag_list)
+    end
+
     def only_for?(project)
       projects == [project]
     end
@@ -111,5 +117,17 @@ module Ci
           'can not be empty when runner is not allowed to pick untagged jobs')
       end
     end
+
+    def not_locked_or_locked_to?(project)
+      !locked? || projects.exists?(id: project.id)
+    end
+
+    def run_untagged_or_has_tags?(build)
+      run_untagged? || build.has_tags?
+    end
+
+    def accepting_tags?(target_tags)
+      (target_tags - tag_list).empty?
+    end
   end
 end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 7660ea2659c714b7baaaabf265ef7d7d9ca2026f..8cd1ffae9ce1cb2687fc428ebcc00f66fc7a2cb2 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -296,16 +296,67 @@ describe Ci::Build, models: true do
         it_behaves_like 'tagged build picker'
       end
 
-      context 'when runner can not pick untagged jobs' do
+      context 'when runner cannot pick untagged jobs' do
         before { runner.run_untagged = false }
 
-        it 'can not handle builds without tags' do
+        it 'cannot handle builds without tags' do
           expect(build.can_be_served?(runner)).to be_falsey
         end
 
         it_behaves_like 'tagged build picker'
       end
     end
+
+    context 'when runner is locked' do
+      before { runner.locked = true }
+
+      shared_examples 'locked build picker' do |serve_matching_tags|
+        context 'when runner cannot pick untagged jobs' do
+          before { runner.run_untagged = false }
+
+          it 'cannot handle builds without tags' do
+            expect(build.can_be_served?(runner)).to be_falsey
+          end
+        end
+
+        context 'when having runner tags' do
+          before { runner.tag_list = ['bb', 'cc'] }
+
+          it "#{serve_matching_tags} handle it for matching tags" do
+            build.tag_list = ['bb']
+            expected = if serve_matching_tags
+                         be_truthy
+                       else
+                         be_falsey
+                       end
+            expect(build.can_be_served?(runner)).to expected
+          end
+
+          it 'cannot handle it for builds without matching tags' do
+            build.tag_list = ['aa']
+            expect(build.can_be_served?(runner)).to be_falsey
+          end
+        end
+      end
+
+      context 'when serving the same project' do
+        it 'can handle it' do
+          expect(build.can_be_served?(runner)).to be_truthy
+        end
+
+        it_behaves_like 'locked build picker', true
+      end
+
+      context 'serving a different project' do
+        before { runner.runner_projects.destroy_all }
+
+        it 'cannot handle it' do
+          expect(build.can_be_served?(runner)).to be_falsey
+        end
+
+        it_behaves_like 'locked build picker', false
+      end
+    end
   end
 
   describe '#has_tags?' do