diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index e226847d2e4125899436b5f342fa64d8ca989d85..9d5239e15937068556291d8d11706443f957cc91 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -14,16 +14,11 @@ module Chain
           :chat_data, :allow_mirror_update, :bridge, :content, :dry_run, :logger, :execution_policy_dry_run,
           # These attributes are set by Chains during processing:
           :config_content, :yaml_processor_result, :workflow_rules_result, :pipeline_seed,
-          :pipeline_config, :execution_policy_pipelines, :partition_id
+          :pipeline_config, :execution_policy_pipelines, :partition_id,
+          keyword_init: true
         ) do
           include Gitlab::Utils::StrongMemoize
 
-          def initialize(params = {})
-            params.each do |key, value|
-              self[key] = value
-            end
-          end
-
           def dry_run?
             dry_run || execution_policy_dry_run
           end
diff --git a/spec/lib/gitlab/relative_positioning/range_spec.rb b/spec/lib/gitlab/relative_positioning/range_spec.rb
index cb3e1504a7aa2a987de65bdd746c974e2a2d57ba..f1951f53934a45eddd11cc8d85bd93b75587baba 100644
--- a/spec/lib/gitlab/relative_positioning/range_spec.rb
+++ b/spec/lib/gitlab/relative_positioning/range_spec.rb
@@ -95,7 +95,7 @@
     item_c = position_struct.new(150, :z, true)
     item_d = position_struct.new(050, :w, true)
     item_e = position_struct.new(250, :r, true)
-    item_f = position_struct.new(positioned?: false)
+    item_f = position_struct.new(nil, nil, false)
     item_ax = position_struct.new(100, :not_x, true)
     item_bx = position_struct.new(200, :not_y, true)
 
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index d426e20af6979aec886663711bd154efe4f69fa0..65137f1bc2ee34c0bf475638f89926d6b2aa369c 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -21,6 +21,8 @@
 require './spec/crystalball_env'
 CrystalballEnv.start!
 
+require_relative 'support/struct_with_kwargs'
+
 ENV["RAILS_ENV"] = 'test'
 ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
 ENV["RSPEC_ALLOW_INVALID_URLS"] = 'true'
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index dfc881caddce425d3609092762b7bc1231bf64b3..4036ef51b2e9ee2097babd3bb5766a2c07ecaf4f 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -3,6 +3,7 @@
 require_relative 'rake'
 require_relative 'rspec_order'
 require_relative 'rspec_run_time'
+require_relative 'struct_with_kwargs'
 require_relative 'system_exit_detected'
 require_relative 'helpers/stub_configuration'
 require_relative 'helpers/stub_metrics'
diff --git a/spec/support/struct_with_kwargs.rb b/spec/support/struct_with_kwargs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ef86c8490eaa039890b8c295e13c92cf1d8dab45
--- /dev/null
+++ b/spec/support/struct_with_kwargs.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# Detect incompatbilities between Ruby 3.1 and Ruby 3.2+ in the cases where
+# Struct.new is missing `keyword_init: true` and a Hash is passed.
+#
+# See https://gitlab.com/gitlab-org/gitlab/-/issues/474743
+module StructWithKwargs
+  # Excluding structs we don't own and cannot patch.
+  EXCLUDE = %w[
+    Aws::S3::EndpointParameters
+  ].freeze
+
+  def self.check?(klass)
+    !EXCLUDE.include?(klass.name) # rubocop:disable Rails/NegateInclude -- Rails is not always available.
+  end
+
+  module Patch
+    def new(*args, **kwargs)
+      return super if kwargs[:keyword_init]
+
+      super.prepend KwargsCheck
+    end
+  end
+
+  module KwargsCheck
+    def initialize(*args, **kwargs, &block)
+      if args.empty? && kwargs.any? && StructWithKwargs.check?(self.class)
+        raise <<~MESSAGE
+          Passing only keyword arguments to #{self.class}#initialize will behave differently from Ruby 3.2. Please pass `keyword_init: true` to `Struct` constructor or use a Hash literal like .new({k: v}) instead of .new(k: v).
+        MESSAGE
+      end
+
+      super
+    end
+  end
+end
+
+Struct.singleton_class.prepend StructWithKwargs::Patch
diff --git a/spec/support_specs/struct_with_kwargs_spec.rb b/spec/support_specs/struct_with_kwargs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..02ea87f134d461f8f3ae46d7ba091ad955b626db
--- /dev/null
+++ b/spec/support_specs/struct_with_kwargs_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+RSpec.describe StructWithKwargs, feature_category: :tooling do
+  let(:const_name) { 'TEST_STRUCT' }
+
+  before do
+    stub_const(const_name, struct)
+  end
+
+  context 'with lack of keyword_init: true' do
+    subject(:struct) { Struct.new(:foo) }
+
+    it { is_expected.to include(described_class::KwargsCheck) }
+
+    it 'accepts plain values' do
+      expect(struct.new(23)).to have_attributes(foo: 23)
+    end
+
+    it 'accepts hash' do
+      expect(struct.new({ foo: 23 })).to have_attributes(foo: { foo: 23 })
+    end
+
+    it 'raises with kwargs' do
+      expect { struct.new(foo: 23) }
+        .to raise_error(RuntimeError, /Passing only keyword arguments to TEST_STRUCT#initialize/)
+    end
+
+    context 'and also positional arguments' do
+      subject(:struct) { Struct.new(:foo, :bar) }
+
+      it 'accepts mix of plain and hash' do
+        expect(struct.new(1, x: 23)).to have_attributes(foo: 1, bar: { x: 23 })
+      end
+
+      it 'raises with kwargs only' do
+        expect { struct.new(foo: 23, bar: 42) }
+          .to raise_error(RuntimeError, /Passing only keyword arguments to TEST_STRUCT#initialize/)
+      end
+    end
+  end
+
+  context 'with Struct.new(..., keyword_init: true)' do
+    subject(:struct) { Struct.new(:foo, keyword_init: true) }
+
+    it { is_expected.not_to include(described_class::KwargsCheck) }
+
+    it 'accepts kwargs or hash', :aggregate_failures do
+      expect(struct.new(foo: 23)).to have_attributes(foo: 23)
+      expect(struct.new({ foo: 23 })).to have_attributes(foo: 23)
+    end
+  end
+
+  describe 'excludes' do
+    where(:exclude) { described_class::EXCLUDE }
+
+    with_them do
+      let(:const_name) { exclude }
+
+      subject(:struct) do
+        Struct.new(:a) do
+          # Simulate libraries' implementation.
+          def initialize(**kwargs)
+            super()
+
+            kwargs.each do |key, value|
+              self[key] = value
+            end
+          end
+        end
+      end
+
+      it { is_expected.to include(described_class::KwargsCheck) }
+
+      it 'accepts hash' do
+        expect(struct.new(a: 23)).to have_attributes(a: 23)
+      end
+    end
+  end
+end