diff --git a/meta/classes-global/base.bbclass b/meta/classes-global/base.bbclass
index 3d5d1c12..c61e59b4 100644
--- a/meta/classes-global/base.bbclass
+++ b/meta/classes-global/base.bbclass
@@ -341,12 +341,25 @@ def base_set_filespath(path, d):
 def calculate_build_uuid(d):
     import uuid
 
-    uuid_data = bb.persist_data.persist('BB_ISAR_UUID_DATA', d)
-    if "uuid" not in uuid_data or not uuid_data["uuid"]:
-        # Generate new UUID
-        uuid_data["uuid"] = str(uuid.uuid4())
-
-    return uuid_data["uuid"]
+    uuid_file = os.path.join(d.getVar('PERSISTENT_DIR'), 'isar-build-uuid')
+    with bb.utils.fileslocked([uuid_file + '.lock']):
+        try:
+            with open(uuid_file) as file:
+                build_uuid = file.read().strip()
+        except FileNotFoundError:
+            build_uuid = ''
+
+        if not build_uuid:
+            build_uuid = str(uuid.uuid4())
+            with open(uuid_file, 'w') as file:
+                file.write(build_uuid + '\n')
+
+        return build_uuid
+
+def clear_build_uuid(d):
+    uuid_file = os.path.join(d.getVar('PERSISTENT_DIR'), 'isar-build-uuid')
+    with bb.utils.fileslocked([uuid_file + '.lock']):
+        bb.utils.remove(uuid_file)
 
 # Unique ID for this build, used to avoid name clashes on external mountpoints
 # When running parallel builds in different PID namespaces
diff --git a/meta/classes-global/isar-events.bbclass b/meta/classes-global/isar-events.bbclass
index 15bfdb99..cfe6d7dd 100644
--- a/meta/classes-global/isar-events.bbclass
+++ b/meta/classes-global/isar-events.bbclass
@@ -85,7 +85,7 @@ python build_completed() {
                 bb.error(str(e))
 
     # Cleanup build UUID, the next bitbake run will generate new one
-    bb.persist_data.persist('BB_ISAR_UUID_DATA', d).clear()
+    bb.utils.exec_flat_python_func('clear_build_uuid', d)
 }
 
 build_completed[eventmask] = "bb.event.BuildCompleted"
diff --git a/testsuite/unittests/test_build_uuid.py b/testsuite/unittests/test_build_uuid.py
new file mode 100644
index 00000000..374cce64
--- /dev/null
+++ b/testsuite/unittests/test_build_uuid.py
@@ -0,0 +1,89 @@
+# This software is a part of Isar.
+# Copyright (C) Siemens AG, 2026
+#
+# SPDX-License-Identifier: MIT
+
+import multiprocessing
+import os
+import sys
+import tempfile
+import unittest
+import uuid
+from pathlib import Path
+
+root = Path(__file__).resolve().parents[2]
+sys.path.extend([
+    str(root / 'bitbake/lib'),
+    str(root / 'meta/lib'),
+])
+
+import bb
+import bb.data
+import bb.utils
+from bb.parse import handle
+
+
+data = bb.data.init()
+data.setVar('BBPATH', f'{root}/meta:{root}/bitbake')
+data.setVar('__bbclasstype', 'global')
+parsed = handle(str(root / 'meta/classes-global/base.bbclass'), data)
+namespace = {'bb': bb, 'os': os}
+exec(parsed['calculate_build_uuid'], namespace)
+exec(parsed['clear_build_uuid'], namespace)
+calculate_build_uuid = namespace['calculate_build_uuid']
+clear_build_uuid = namespace['clear_build_uuid']
+
+
+def calculate_in_worker(persistent_dir, start, results):
+    data = bb.data.init()
+    data.setVar('PERSISTENT_DIR', persistent_dir)
+    start.wait()
+    results.put(calculate_build_uuid(data))
+
+
+class TestBuildUUID(unittest.TestCase):
+    def setUp(self):
+        directory = tempfile.TemporaryDirectory()
+        self.addCleanup(directory.cleanup)
+        self.persistent_dir = os.path.join(directory.name, 'cache')
+        self.data = bb.data.init()
+        self.data.setVar('PERSISTENT_DIR', self.persistent_dir)
+
+    def test_uuid_is_reused_across_calls_and_multiconfigs(self):
+        build_uuid = calculate_build_uuid(self.data)
+        self.assertEqual(str(uuid.UUID(build_uuid)), build_uuid)
+        self.assertEqual(calculate_build_uuid(self.data), build_uuid)
+        other_config = self.data.createCopy()
+        other_config.setVar('BB_CURRENT_MC', 'other')
+        self.assertEqual(calculate_build_uuid(other_config), build_uuid)
+        clear_build_uuid(self.data)
+        self.assertNotEqual(calculate_build_uuid(self.data), build_uuid)
+
+    def test_concurrent_workers_share_uuid(self):
+        context = multiprocessing.get_context('fork')
+        start = context.Event()
+        results = context.Queue()
+        workers = [
+            context.Process(
+                target=calculate_in_worker,
+                args=(self.persistent_dir, start, results),
+            )
+            for _ in range(8)
+        ]
+        try:
+            for worker in workers:
+                worker.start()
+            start.set()
+            values = [results.get(timeout=15) for worker in workers]
+            for worker in workers:
+                worker.join(timeout=15)
+                self.assertEqual(worker.exitcode, 0)
+            self.assertEqual(len(set(values)), 1)
+            self.assertEqual(values[0], calculate_build_uuid(self.data))
+        finally:
+            for worker in workers:
+                if worker.is_alive():
+                    worker.terminate()
+                worker.join(timeout=15)
+            results.close()
+            results.join_thread()
