diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index d99774e8e7e20acf3e68dbc531734a14867411cb..a72606b3584d31ebf7a80c293daac357de831377 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -2263,25 +2263,6 @@ Style/SlicingWithRange:
     - 'lib/mastodon/premailer_webpack_strategy.rb'
     - 'lib/tasks/repo.rake'
 
-# Offense count: 25
-# This cop supports unsafe autocorrection (--autocorrect-all).
-# Configuration parameters: Mode.
-Style/StringConcatenation:
-  Exclude:
-    - 'app/lib/activitypub/case_transform.rb'
-    - 'app/lib/validation_error_formatter.rb'
-    - 'app/services/backup_service.rb'
-    - 'app/services/fetch_link_card_service.rb'
-    - 'lib/mastodon/emoji_cli.rb'
-    - 'lib/mastodon/redis_config.rb'
-    - 'lib/mastodon/snowflake.rb'
-    - 'lib/paperclip/gif_transcoder.rb'
-    - 'lib/paperclip/type_corrector.rb'
-    - 'spec/controllers/api/v1/apps_controller_spec.rb'
-    - 'spec/controllers/api/v1/streaming_controller_spec.rb'
-    - 'spec/validators/disallowed_hashtags_validator_spec.rb'
-    - 'spec/workers/web/push_notification_worker_spec.rb'
-
 # Offense count: 272
 # This cop supports safe autocorrection (--autocorrect).
 # Configuration parameters: EnforcedStyle, MinSize.
diff --git a/app/lib/activitypub/case_transform.rb b/app/lib/activitypub/case_transform.rb
index 7f716f86243ef83353d15683273b173a8a72f7f7..d36e01b8f2ec3be8b6ae7f843208147a9fdf1d3e 100644
--- a/app/lib/activitypub/case_transform.rb
+++ b/app/lib/activitypub/case_transform.rb
@@ -13,7 +13,7 @@ module ActivityPub::CaseTransform
       when Symbol then camel_lower(value.to_s).to_sym
       when String
         camel_lower_cache[value] ||= if value.start_with?('_:')
-                                       '_:' + value.gsub(/\A_:/, '').underscore.camelize(:lower)
+                                       "_:#{value.gsub(/\A_:/, '').underscore.camelize(:lower)}"
                                      else
                                        value.underscore.camelize(:lower)
                                      end
diff --git a/app/lib/validation_error_formatter.rb b/app/lib/validation_error_formatter.rb
index 3f964f739b559ee3622e82d47538b253e890609d..1d3e8955b3ce8bb065ec99f33a480b632ef674f9 100644
--- a/app/lib/validation_error_formatter.rb
+++ b/app/lib/validation_error_formatter.rb
@@ -19,7 +19,7 @@ class ValidationErrorFormatter
       messages = errors.messages[attribute_name]
 
       h[@aliases[attribute_name] || attribute_name] = attribute_errors.map.with_index do |error, index|
-        { error: 'ERR_' + error[:error].to_s.upcase, description: messages[index] }
+        { error: "ERR_#{error[:error].to_s.upcase}", description: messages[index] }
       end
     end
 
diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb
index b880dfbe71e54a296e36dd86e9da88594056aabe..5498cdd455e0eb919eadd8099b9523f887302615 100644
--- a/app/services/backup_service.rb
+++ b/app/services/backup_service.rb
@@ -53,7 +53,7 @@ class BackupService < BaseService
       end
     end
 
-    archive_filename = ['archive', Time.now.utc.strftime('%Y%m%d%H%M%S'), SecureRandom.hex(16)].join('-') + '.tar.gz'
+    archive_filename = "#{['archive', Time.now.utc.strftime('%Y%m%d%H%M%S'), SecureRandom.hex(16)].join('-')}.tar.gz"
 
     @backup.dump      = ActionDispatch::Http::UploadedFile.new(tempfile: tmp_file, filename: archive_filename)
     @backup.processed = true
@@ -86,14 +86,14 @@ class BackupService < BaseService
   def dump_actor!(tar)
     actor = serialize(account, ActivityPub::ActorSerializer)
 
-    actor[:icon][:url]  = 'avatar' + File.extname(actor[:icon][:url])  if actor[:icon]
-    actor[:image][:url] = 'header' + File.extname(actor[:image][:url]) if actor[:image]
+    actor[:icon][:url]  = "avatar#{File.extname(actor[:icon][:url])}"  if actor[:icon]
+    actor[:image][:url] = "header#{File.extname(actor[:image][:url])}" if actor[:image]
     actor[:outbox]      = 'outbox.json'
     actor[:likes]       = 'likes.json'
     actor[:bookmarks]   = 'bookmarks.json'
 
-    download_to_tar(tar, account.avatar, 'avatar' + File.extname(account.avatar.path)) if account.avatar.exists?
-    download_to_tar(tar, account.header, 'header' + File.extname(account.header.path)) if account.header.exists?
+    download_to_tar(tar, account.avatar, "avatar#{File.extname(account.avatar.path)}") if account.avatar.exists?
+    download_to_tar(tar, account.header, "header#{File.extname(account.header.path)}") if account.header.exists?
 
     json = Oj.dump(actor)
 
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index d5fa9af5410b27d817270bb507f83a88ff057608..8d07958b7335df7ed3dfd0c489288f4fe15b72bc 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -45,7 +45,7 @@ class FetchLinkCardService < BaseService
   def html
     return @html if defined?(@html)
 
-    Request.new(:get, @url).add_headers('Accept' => 'text/html', 'User-Agent' => Mastodon::Version.user_agent + ' Bot').perform do |res|
+    Request.new(:get, @url).add_headers('Accept' => 'text/html', 'User-Agent' => "#{Mastodon::Version.user_agent} Bot").perform do |res|
       # We follow redirects, and ideally we want to save the preview card for
       # the destination URL and not any link shortener in-between, so here
       # we set the URL to the one of the last response in the redirect chain
diff --git a/lib/mastodon/emoji_cli.rb b/lib/mastodon/emoji_cli.rb
index feb77107fb61a2f808c2560d5c2b3dbfc70b9b47..88065c2a39e5f55d24f86c7e9140486f1d094e75 100644
--- a/lib/mastodon/emoji_cli.rb
+++ b/lib/mastodon/emoji_cli.rb
@@ -68,7 +68,7 @@ module Mastodon
             failed += 1
             say('Failure/Error: ', :red)
             say(entry.full_name)
-            say('    ' + custom_emoji.errors[:image].join(', '), :red)
+            say("    #{custom_emoji.errors[:image].join(', ')}", :red)
           end
         end
       end
diff --git a/lib/mastodon/redis_config.rb b/lib/mastodon/redis_config.rb
index 037ca5edc9b6c49bb183c1f4ab724976be7138a2..3e97da873a57bc0705e9afb8b725328564d1555b 100644
--- a/lib/mastodon/redis_config.rb
+++ b/lib/mastodon/redis_config.rb
@@ -1,17 +1,17 @@
 # frozen_string_literal: true
 
 def setup_redis_env_url(prefix = nil, defaults = true)
-  prefix = prefix.to_s.upcase + '_' unless prefix.nil?
+  prefix = "#{prefix.to_s.upcase}_" unless prefix.nil?
   prefix = '' if prefix.nil?
 
-  return if ENV[prefix + 'REDIS_URL'].present?
+  return if ENV["#{prefix}REDIS_URL"].present?
 
-  password = ENV.fetch(prefix + 'REDIS_PASSWORD') { '' if defaults }
-  host     = ENV.fetch(prefix + 'REDIS_HOST') { 'localhost' if defaults }
-  port     = ENV.fetch(prefix + 'REDIS_PORT') { 6379 if defaults }
-  db       = ENV.fetch(prefix + 'REDIS_DB') { 0 if defaults }
+  password = ENV.fetch("#{prefix}REDIS_PASSWORD") { '' if defaults }
+  host     = ENV.fetch("#{prefix}REDIS_HOST") { 'localhost' if defaults }
+  port     = ENV.fetch("#{prefix}REDIS_PORT") { 6379 if defaults }
+  db       = ENV.fetch("#{prefix}REDIS_DB") { 0 if defaults }
 
-  ENV[prefix + 'REDIS_URL'] = begin
+  ENV["#{prefix}REDIS_URL"] = begin
     if [password, host, port, db].all?(&:nil?)
       ENV['REDIS_URL']
     else
@@ -27,7 +27,7 @@ setup_redis_env_url(:cache, false)
 setup_redis_env_url(:sidekiq, false)
 
 namespace         = ENV.fetch('REDIS_NAMESPACE', nil)
-cache_namespace   = namespace ? namespace + '_cache' : 'cache'
+cache_namespace   = namespace ? "#{namespace}_cache" : 'cache'
 sidekiq_namespace = namespace
 
 REDIS_CACHE_PARAMS = {
diff --git a/lib/mastodon/snowflake.rb b/lib/mastodon/snowflake.rb
index fe0dc1722eeb377eebbdafb286632ffc35983a56..8030288aff700834747eb394ec27f648b61a887d 100644
--- a/lib/mastodon/snowflake.rb
+++ b/lib/mastodon/snowflake.rb
@@ -115,7 +115,7 @@ module Mastodon::Snowflake
         # And only those that are using timestamp_id.
         next unless (data = DEFAULT_REGEX.match(id_col.default_function))
 
-        seq_name = data[:seq_prefix] + '_id_seq'
+        seq_name = "#{data[:seq_prefix]}_id_seq"
 
         # If we were on Postgres 9.5+, we could do CREATE SEQUENCE IF
         # NOT EXISTS, but we can't depend on that. Instead, catch the
diff --git a/lib/paperclip/gif_transcoder.rb b/lib/paperclip/gif_transcoder.rb
index f385b00a3213f3abfb91f7f909c22d4f90494fb9..32bdb8a8638fe895e3ea51597c22f33a7a1b0cda 100644
--- a/lib/paperclip/gif_transcoder.rb
+++ b/lib/paperclip/gif_transcoder.rb
@@ -109,7 +109,7 @@ module Paperclip
       final_file = Paperclip::Transcoder.make(file, options, attachment)
 
       if options[:style] == :original
-        attachment.instance.file_file_name    = File.basename(attachment.instance.file_file_name, '.*') + '.mp4'
+        attachment.instance.file_file_name    = "#{File.basename(attachment.instance.file_file_name, '.*')}.mp4"
         attachment.instance.file_content_type = 'video/mp4'
         attachment.instance.type              = MediaAttachment.types[:gifv]
       end
diff --git a/lib/paperclip/type_corrector.rb b/lib/paperclip/type_corrector.rb
index 17e2fc5daaa147779c57742ce7ad774a4f995581..030b98b122b3dca975c0a494fff4423e19369a6a 100644
--- a/lib/paperclip/type_corrector.rb
+++ b/lib/paperclip/type_corrector.rb
@@ -7,7 +7,7 @@ module Paperclip
     def make
       return @file unless options[:format]
 
-      target_extension = '.' + options[:format]
+      target_extension = ".#{options[:format]}"
       extension        = File.extname(attachment.instance_read(:file_name))
 
       return @file unless options[:style] == :original && target_extension && extension != target_extension
diff --git a/spec/controllers/api/v1/apps_controller_spec.rb b/spec/controllers/api/v1/apps_controller_spec.rb
index 9ac7880a4ab261e5df429ddf0580dc16bc18963b..61158e881fee15050668865b5dbdc0f44456fdf9 100644
--- a/spec/controllers/api/v1/apps_controller_spec.rb
+++ b/spec/controllers/api/v1/apps_controller_spec.rb
@@ -68,7 +68,7 @@ RSpec.describe Api::V1::AppsController, type: :controller do
     end
 
     context 'with a too-long website' do
-      let(:website) { 'https://foo.bar/' + ('hoge' * 2_000) }
+      let(:website) { "https://foo.bar/#{'hoge' * 2_000}" }
 
       it 'returns http unprocessable entity' do
         expect(response).to have_http_status(422)
@@ -76,7 +76,7 @@ RSpec.describe Api::V1::AppsController, type: :controller do
     end
 
     context 'with a too-long redirect_uris' do
-      let(:redirect_uris) { 'https://foo.bar/' + ('hoge' * 2_000) }
+      let(:redirect_uris) { "https://foo.bar/#{'hoge' * 2_000}" }
 
       it 'returns http unprocessable entity' do
         expect(response).to have_http_status(422)
diff --git a/spec/controllers/api/v1/streaming_controller_spec.rb b/spec/controllers/api/v1/streaming_controller_spec.rb
index 9dbca0178589f60ce4f34f47c58bfc67b929931f..7014ed9b2bead8293bd06ba7424ec499416286a7 100644
--- a/spec/controllers/api/v1/streaming_controller_spec.rb
+++ b/spec/controllers/api/v1/streaming_controller_spec.rb
@@ -25,7 +25,7 @@ describe Api::V1::StreamingController do
 
   context 'with streaming api on different host' do
     before(:each) do
-      Rails.configuration.x.streaming_api_base_url = 'wss://streaming-' + Rails.configuration.x.web_domain
+      Rails.configuration.x.streaming_api_base_url = "wss://streaming-#{Rails.configuration.x.web_domain}"
       @streaming_host = URI.parse(Rails.configuration.x.streaming_api_base_url).host
     end
 
diff --git a/spec/validators/disallowed_hashtags_validator_spec.rb b/spec/validators/disallowed_hashtags_validator_spec.rb
index 2c4ebc4f25450a0c1653d906ff2ce5420f064487..896fd4fc5ee4b2f0bec495c69a6c7763307e8a5e 100644
--- a/spec/validators/disallowed_hashtags_validator_spec.rb
+++ b/spec/validators/disallowed_hashtags_validator_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe DisallowedHashtagsValidator, type: :validator do
       described_class.new.validate(status)
     end
 
-    let(:status) { double(errors: errors, local?: local, reblog?: reblog, text: disallowed_tags.map { |x| '#' + x }.join(' ')) }
+    let(:status) { double(errors: errors, local?: local, reblog?: reblog, text: disallowed_tags.map { |x| "##{x}" }.join(' ')) }
     let(:errors) { double(add: nil) }
 
     context 'for a remote reblog' do
diff --git a/spec/workers/web/push_notification_worker_spec.rb b/spec/workers/web/push_notification_worker_spec.rb
index 5bc24f8886152fd684035126a8757b0073ee20c2..822ef5257f4a7d0d558248113ba53288d470c237 100644
--- a/spec/workers/web/push_notification_worker_spec.rb
+++ b/spec/workers/web/push_notification_worker_spec.rb
@@ -37,7 +37,7 @@ describe Web::PushNotificationWorker do
       expect(a_request(:post, endpoint).with(headers: {
         'Content-Encoding' => 'aesgcm',
         'Content-Type' => 'application/octet-stream',
-        'Crypto-Key' => 'dh=BAgtUks5d90kFmxGevk9tH7GEmvz9DB0qcEMUsOBgKwMf-TMjsKIIG6LQvGcFAf6jcmAod15VVwmYwGIIxE4VWE;p256ecdsa=' + vapid_public_key.delete('='),
+        'Crypto-Key' => "dh=BAgtUks5d90kFmxGevk9tH7GEmvz9DB0qcEMUsOBgKwMf-TMjsKIIG6LQvGcFAf6jcmAod15VVwmYwGIIxE4VWE;p256ecdsa=#{vapid_public_key.delete('=')}",
         'Encryption' => 'salt=WJeVM-RY-F9351SVxTFx_g',
         'Ttl' => '172800',
         'Urgency' => 'normal',