diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index 89ab5c6bd3b76..4edfd111e5cf1 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -676,6 +676,7 @@ fn list_metadata(early_dcx: &EarlyDiagCtxt, sess: &Session, metadata_loader: &dy
                 metadata_loader,
                 &mut v,
                 &sess.opts.unstable_opts.ls,
+                sess.cfg_version,
             )
             .unwrap();
             safe_println!("{}", String::from_utf8(v).unwrap());
diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs
index dcccace12b00a..10bbebb3871e3 100644
--- a/compiler/rustc_metadata/src/locator.rs
+++ b/compiler/rustc_metadata/src/locator.rs
@@ -569,31 +569,47 @@ impl<'a> CrateLocator<'a> {
                 debug!("skipping empty file");
                 continue;
             }
-            let (hash, metadata) =
-                match get_metadata_section(self.target, flavor, &lib, self.metadata_loader) {
-                    Ok(blob) => {
-                        if let Some(h) = self.crate_matches(&blob, &lib) {
-                            (h, blob)
-                        } else {
-                            info!("metadata mismatch");
-                            continue;
-                        }
-                    }
-                    Err(MetadataError::LoadFailure(err)) => {
-                        info!("no metadata found: {}", err);
-                        // The file was present and created by the same compiler version, but we
-                        // couldn't load it for some reason. Give a hard error instead of silently
-                        // ignoring it, but only if we would have given an error anyway.
-                        self.crate_rejections
-                            .via_invalid
-                            .push(CrateMismatch { path: lib, got: err });
-                        continue;
-                    }
-                    Err(err @ MetadataError::NotPresent(_)) => {
-                        info!("no metadata found: {}", err);
+            let (hash, metadata) = match get_metadata_section(
+                self.target,
+                flavor,
+                &lib,
+                self.metadata_loader,
+                self.cfg_version,
+            ) {
+                Ok(blob) => {
+                    if let Some(h) = self.crate_matches(&blob, &lib) {
+                        (h, blob)
+                    } else {
+                        info!("metadata mismatch");
                         continue;
                     }
-                };
+                }
+                Err(MetadataError::VersionMismatch { expected_version, found_version }) => {
+                    // The file was present and created by the same compiler version, but we
+                    // couldn't load it for some reason. Give a hard error instead of silently
+                    // ignoring it, but only if we would have given an error anyway.
+                    info!(
+                        "Rejecting via version: expected {} got {}",
+                        expected_version, found_version
+                    );
+                    self.crate_rejections
+                        .via_version
+                        .push(CrateMismatch { path: lib, got: found_version });
+                    continue;
+                }
+                Err(MetadataError::LoadFailure(err)) => {
+                    info!("no metadata found: {}", err);
+                    // The file was present and created by the same compiler version, but we
+                    // couldn't load it for some reason. Give a hard error instead of silently
+                    // ignoring it, but only if we would have given an error anyway.
+                    self.crate_rejections.via_invalid.push(CrateMismatch { path: lib, got: err });
+                    continue;
+                }
+                Err(err @ MetadataError::NotPresent(_)) => {
+                    info!("no metadata found: {}", err);
+                    continue;
+                }
+            };
             // If we see multiple hashes, emit an error about duplicate candidates.
             if slot.as_ref().is_some_and(|s| s.0 != hash) {
                 if let Some(candidates) = err_data {
@@ -648,16 +664,6 @@ impl<'a> CrateLocator<'a> {
     }
 
     fn crate_matches(&mut self, metadata: &MetadataBlob, libpath: &Path) -> Option<Svh> {
-        let rustc_version = rustc_version(self.cfg_version);
-        let found_version = metadata.get_rustc_version();
-        if found_version != rustc_version {
-            info!("Rejecting via version: expected {} got {}", rustc_version, found_version);
-            self.crate_rejections
-                .via_version
-                .push(CrateMismatch { path: libpath.to_path_buf(), got: found_version });
-            return None;
-        }
-
         let header = metadata.get_header();
         if header.is_proc_macro_crate != self.is_proc_macro {
             info!(
@@ -770,6 +776,7 @@ fn get_metadata_section<'p>(
     flavor: CrateFlavor,
     filename: &'p Path,
     loader: &dyn MetadataLoader,
+    cfg_version: &'static str,
 ) -> Result<MetadataBlob, MetadataError<'p>> {
     if !filename.exists() {
         return Err(MetadataError::NotPresent(filename));
@@ -847,13 +854,18 @@ fn get_metadata_section<'p>(
         }
     };
     let blob = MetadataBlob(raw_bytes);
-    if blob.is_compatible() {
-        Ok(blob)
-    } else {
-        Err(MetadataError::LoadFailure(format!(
+    match blob.check_compatibility(cfg_version) {
+        Ok(()) => Ok(blob),
+        Err(None) => Err(MetadataError::LoadFailure(format!(
             "invalid metadata version found: {}",
             filename.display()
-        )))
+        ))),
+        Err(Some(found_version)) => {
+            return Err(MetadataError::VersionMismatch {
+                expected_version: rustc_version(cfg_version),
+                found_version,
+            });
+        }
     }
 }
 
@@ -864,9 +876,10 @@ pub fn list_file_metadata(
     metadata_loader: &dyn MetadataLoader,
     out: &mut dyn Write,
     ls_kinds: &[String],
+    cfg_version: &'static str,
 ) -> IoResult<()> {
     let flavor = get_flavor_from_path(path);
-    match get_metadata_section(target, flavor, path, metadata_loader) {
+    match get_metadata_section(target, flavor, path, metadata_loader, cfg_version) {
         Ok(metadata) => metadata.list_crate_metadata(out, ls_kinds),
         Err(msg) => write!(out, "{msg}\n"),
     }
@@ -932,6 +945,8 @@ enum MetadataError<'a> {
     NotPresent(&'a Path),
     /// The file was present and invalid.
     LoadFailure(String),
+    /// The file was present, but compiled with a different rustc version.
+    VersionMismatch { expected_version: String, found_version: String },
 }
 
 impl fmt::Display for MetadataError<'_> {
@@ -941,6 +956,12 @@ impl fmt::Display for MetadataError<'_> {
                 f.write_str(&format!("no such file: '{}'", filename.display()))
             }
             MetadataError::LoadFailure(msg) => f.write_str(msg),
+            MetadataError::VersionMismatch { expected_version, found_version } => {
+                f.write_str(&format!(
+                    "rustc version mismatch. expected {}, found {}",
+                    expected_version, found_version,
+                ))
+            }
         }
     }
 }
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index da384007c22c2..0467cf2969fc5 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -684,13 +684,25 @@ impl<'a, 'tcx, I: Idx, T> Decodable<DecodeContext<'a, 'tcx>> for LazyTable<I, T>
 implement_ty_decoder!(DecodeContext<'a, 'tcx>);
 
 impl MetadataBlob {
-    pub(crate) fn is_compatible(&self) -> bool {
-        self.blob().starts_with(METADATA_HEADER)
-    }
+    pub(crate) fn check_compatibility(
+        &self,
+        cfg_version: &'static str,
+    ) -> Result<(), Option<String>> {
+        if !self.blob().starts_with(METADATA_HEADER) {
+            if self.blob().starts_with(b"rust") {
+                return Err(Some("<unknown rustc version>".to_owned()));
+            }
+            return Err(None);
+        }
 
-    pub(crate) fn get_rustc_version(&self) -> String {
-        LazyValue::<String>::from_position(NonZero::new(METADATA_HEADER.len() + 8).unwrap())
-            .decode(self)
+        let found_version =
+            LazyValue::<String>::from_position(NonZero::new(METADATA_HEADER.len() + 8).unwrap())
+                .decode(self);
+        if rustc_version(cfg_version) != found_version {
+            return Err(Some(found_version));
+        }
+
+        Ok(())
     }
 
     fn root_pos(&self) -> NonZero<usize> {