diff --git a/REFERENCE.md b/REFERENCE.md
index 7aef694b..cb85d76e 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -86,6 +86,7 @@
 * [`ssl_clean`](#ssl_clean): Clean an agent's certificate
 * [`submit_csr`](#submit_csr): Submit a certificate signing request
 * [`transform_classification_groups`](#transform_classification_groups): Transform the user groups from a source backup to a list of groups on the target server
+* [`validate_rbac_token`](#validate_rbac_token): Check an RBAC token stored in a file is valid
 * [`wait_until_service_ready`](#wait_until_service_ready): Return when the orchestrator service is healthy, or timeout after 15 seconds
 
 ### Plans
@@ -1572,6 +1573,20 @@ Data type: `String`
 
 Location of target node group yaml file and where to create the transformed file
 
+### `validate_rbac_token`
+
+Check an RBAC token stored in a file is valid
+
+**Supports noop?** false
+
+#### Parameters
+
+##### `token_file`
+
+Data type: `Optional[String]`
+
+The path to the token file to use
+
 ### `wait_until_service_ready`
 
 Return when the orchestrator service is healthy, or timeout after 15 seconds
diff --git a/plans/upgrade.pp b/plans/upgrade.pp
index 5d061e0d..d15f7e44 100644
--- a/plans/upgrade.pp
+++ b/plans/upgrade.pp
@@ -67,6 +67,8 @@
       'upgrade-replica-compilers',
   'finalize']] $begin_at_step = undef,
 ) {
+  out::message('# Validating inputs')
+
   # Ensure input valid for a supported architecture
   $arch = peadm::assert_supported_architecture(
     $primary_host,
@@ -97,6 +99,11 @@
       $replica_postgresql_target,
   ])
 
+  # Validate the RBAC token used to upgrade compilers if compilers are present
+  if $compiler_targets and $compiler_targets.size > 0 {
+    run_task('peadm::validate_rbac_token', $primary_target, token_file => $token_file)
+  }
+
   out::message('# Gathering information')
 
   # lint:ignore:strict_indent
diff --git a/plans/util/retrieve_and_upload.pp b/plans/util/retrieve_and_upload.pp
index 2bb40168..51002d28 100644
--- a/plans/util/retrieve_and_upload.pp
+++ b/plans/util/retrieve_and_upload.pp
@@ -29,39 +29,39 @@
     |-HEREDOC
     # lint:endignore
 
-$operating_system = run_task('peadm::os_identification', 'local://localhost')
-$os_string =$operating_system.first.value['_output']
+  $operating_system = run_task('peadm::os_identification', 'local://localhost')
+  $os_string =$operating_system.first.value['_output']
 
-if 'windows' in $os_string {
-  $exists = run_command("[System.IO.File]::Exists('${local_path}')", 'local://localhost')
-  if $exists.first['stdout'].chomp == 'false' {
-    run_task('peadm::download', 'local://localhost',
-      source => $source,
-      path   => $local_path,
-    )
-  }
+  if 'windows' in $os_string {
+    $exists = run_command("[System.IO.File]::Exists('${local_path}')", 'local://localhost')
+    if $exists.first['stdout'].chomp == 'false' {
+      run_task('peadm::download', 'local://localhost',
+        source => $source,
+        path   => $local_path,
+      )
+    }
 
-  $result_size = run_task('peadm::filesize', 'local://localhost',
-    path => $local_path,
-  )
-  $local_size = $result_size.first.value['_output']
-} else {
-  $exists = without_default_logging() || {
-    run_command("test -e '${local_path}'", 'local://localhost',
-      _catch_errors => true,
-    ).ok()
-  }
-  unless $exists {
-    run_task('peadm::download', 'local://localhost',
-      source => $source,
-      path   => $local_path,
+    $result_size = run_task('peadm::filesize', 'local://localhost',
+      path => $local_path,
     )
-  }
+    $local_size = $result_size.first.value['_output']
+  } else {
+    $exists = without_default_logging() || {
+      run_command("test -e '${local_path}'", 'local://localhost',
+        _catch_errors => true,
+      ).ok()
+    }
+    unless $exists {
+      run_task('peadm::download', 'local://localhost',
+        source => $source,
+        path   => $local_path,
+      )
+    }
 
-  $local_size = run_task('peadm::filesize', 'local://localhost',
-    path => $local_path,
-  ).first['size']
-}
+    $local_size = run_task('peadm::filesize', 'local://localhost',
+      path => $local_path,
+    ).first['size']
+  }
 
   $targets_needing_file = run_task('peadm::filesize', $nodes,
     path => $upload_path,
diff --git a/tasks/puppet_infra_upgrade.rb b/tasks/puppet_infra_upgrade.rb
index daad14b8..11fea276 100755
--- a/tasks/puppet_infra_upgrade.rb
+++ b/tasks/puppet_infra_upgrade.rb
@@ -73,7 +73,8 @@ def wait_until_connected(nodes:, token_file:, timeout: 120)
       loop do
         response = https.request(request)
         unless response.is_a? Net::HTTPSuccess
-          raise "Unexpected result from orchestrator: #{response.class}\n#{response}"
+          body = JSON.parse(response.body)
+          raise "Unexpected result from orchestrator: #{response.code}#{body.kind}\n#{body.msg}"
         end
         inventory = JSON.parse(response.body)
         break if inventory['items'].all? { |item| item['connected'] }
diff --git a/tasks/validate_rbac_token.json b/tasks/validate_rbac_token.json
new file mode 100644
index 00000000..6c526643
--- /dev/null
+++ b/tasks/validate_rbac_token.json
@@ -0,0 +1,13 @@
+{
+  "description": "Check an RBAC token stored in a file is valid",
+  "parameters": {
+    "token_file": {
+      "type": "Optional[String]",
+      "description": "The path to the token file to use"
+    }
+  },
+  "input_method": "stdin",
+  "implementations": [
+    {"name": "validate_rbac_token.rb"}
+  ]
+}
diff --git a/tasks/validate_rbac_token.rb b/tasks/validate_rbac_token.rb
new file mode 100755
index 00000000..eed23384
--- /dev/null
+++ b/tasks/validate_rbac_token.rb
@@ -0,0 +1,78 @@
+#!/opt/puppetlabs/puppet/bin/ruby
+# frozen_string_literal: true
+
+require 'uri'
+require 'net/https'
+require 'json'
+require 'etc'
+require 'puppet'
+
+# Class to check an rbac token is valid
+class ValidateRbacToken
+  def initialize(params)
+    @token_file = params['token_file']
+  end
+
+  def execute!
+    token_file = @token_file || File.join(Etc.getpwuid.dir, '.puppetlabs', 'token')
+
+    uri = URI("https://#{Puppet.settings[:certname]}:4433/rbac-api/v2/auth/token/authenticate")
+    https = https_object(uri: uri)
+    request = request_object(token_file: token_file)
+
+    resp = https.request(request)
+
+    if resp.code == '200'
+      puts 'RBAC token is valid'
+      exit 0
+    else
+      body = JSON.parse(resp.body)
+      case resp.code
+      when '401', '403'
+        puts "#{resp.code} #{body['kind']}: " \
+             "Check your API token at #{token_file}.\n" \
+             "Please refresh your token or provide an alternate file.\n" \
+             "See https://www.puppet.com/docs/pe/latest/rbac_token_auth_intro for more details.\n"
+      else
+        puts "Error validating token: #{resp.code} #{body['kind']}"
+        puts body['msg']
+      end
+
+      exit 1
+    end
+  end
+
+  def request_object(token_file:)
+    token = File.read(token_file)
+    body = {
+      'token' => token.chomp,
+      'update_last_activity?' => false,
+    }.to_json
+
+    request = Net::HTTP::Post.new('/rbac-api/v2/auth/token/authenticate')
+    request['Content-Type'] = 'application/json'
+    request.body = body
+
+    request
+  end
+
+  def https_object(uri:)
+    https = Net::HTTP.new(uri.host, uri.port)
+    https.use_ssl = true
+    https.cert = OpenSSL::X509::Certificate.new(File.read(Puppet.settings[:hostcert]))
+    https.key = OpenSSL::PKey::RSA.new(File.read(Puppet.settings[:hostprivkey]))
+    https.verify_mode = OpenSSL::SSL::VERIFY_PEER
+    https.ca_file = Puppet.settings[:localcacert]
+
+    https
+  end
+end
+
+# Run the task unless an environment flag has been set, signaling not to. The
+# environment flag is used to disable auto-execution and enable Ruby unit
+# testing of this task.
+unless ENV['RSPEC_UNIT_TEST_MODE']
+  Puppet.initialize_settings
+  validate = ValidateRbacToken.new(JSON.parse(STDIN.read))
+  validate.execute!
+end