diff --git a/daemon/subdocument_context.cc b/daemon/subdocument_context.cc
index 9d8426e06..598878e8c 100644
--- a/daemon/subdocument_context.cc
+++ b/daemon/subdocument_context.cc
@@ -639,7 +639,7 @@ std::string_view SubdocCmdContext::get_xtoc_vattr() {
         cb::xattr::Blob xattr_blob(blob_buffer,
                                    mcbp::datatype::is_snappy(in_datatype));
 
-        nlohmann::json arr;
+        nlohmann::json arr = nlohmann::json::array();
         for (const auto [key, value] : xattr_blob) {
             (void)value;
             bool isSystemXattr = cb::xattr::is_system_xattr(key);
diff --git a/engines/ep/src/kv_bucket.cc b/engines/ep/src/kv_bucket.cc
index 3a7e1ecc1..691f68cef 100644
--- a/engines/ep/src/kv_bucket.cc
+++ b/engines/ep/src/kv_bucket.cc
@@ -1005,7 +1005,6 @@ void KVBucket::setVBucketState_UNLOCKED(
     }
 
     auto oldstate = vbMap.setState_UNLOCKED(*vb, to, meta, vbStateLock);
-    vb->updateStatsForStateChange(oldstate, to);
 
     if (oldstate != to && notify_dcp) {
         bool closeInboundStreams = false;
diff --git a/engines/ep/src/kvstore/couch-kvstore/couch-kvstore.cc b/engines/ep/src/kvstore/couch-kvstore/couch-kvstore.cc
index 35db6417a..1e452a758 100644
--- a/engines/ep/src/kvstore/couch-kvstore/couch-kvstore.cc
+++ b/engines/ep/src/kvstore/couch-kvstore/couch-kvstore.cc
@@ -505,14 +505,15 @@ void CouchKVStore::initialize(
             throw std::runtime_error(msg);
         }
 
-        const auto readRes = readVBStateAndUpdateCache(db, vbid).status;
+        const auto [readRes, state] = readVBStateAndUpdateCache(db, vbid);
 
         switch (readRes) {
         case ReadVBStateStatus::Success:
             // Success, update stats
             ++st.numLoadedVb;
-            cachedDeleteCount[getCacheSlot(vbid)] =
-                    cb::couchstore::getHeader(*db.getDb()).deletedCount;
+            readHeaderAndUpdateCache(*db.getDb(), vbid);
+            cachedOnDiskPrepareSize[getCacheSlot(vbid)] =
+                    state.getOnDiskPrepareBytes();
             break;
         case ReadVBStateStatus::NotFound:
             // No vBucket state found. It's possible for a vBucket header to
@@ -1783,18 +1784,14 @@ bool CouchKVStore::compactDBTryAndSwitchToNewFile(
         hookCtx->completionCallback(*hookCtx);
     }
 
-    auto info = cb::couchstore::getHeader(*targetDb.getDb());
+    auto info = readHeaderAndUpdateCache(*targetDb.getDb(), vbid);
     hookCtx->stats.post = toFileInfo(info);
 
-    cachedFileSize[getCacheSlot(vbid)] = info.fileSize;
-    cachedSpaceUsed[getCacheSlot(vbid)] = info.spaceUsed;
-
     // also update cached state with dbinfo (the disk entry is already updated)
     auto* state = getCachedVBucketState(vbid);
     if (state) {
         state->highSeqno = info.updateSeqNum;
         state->purgeSeqno = info.purgeSeqNum;
-        cachedDeleteCount[getCacheSlot(vbid)] = info.deletedCount;
         state->onDiskPrepares = prepareStats.onDiskPrepares;
         state->setOnDiskPrepareBytes(prepareStats.onDiskPrepareBytes);
         cachedOnDiskPrepareSize[getCacheSlot(vbid)] =
@@ -2074,9 +2071,7 @@ bool CouchKVStore::writeVBucketState(Vbid vbucketId,
         return false;
     }
 
-    const auto info = cb::couchstore::getHeader(*db);
-    cachedSpaceUsed[getCacheSlot(vbucketId)] = info.spaceUsed;
-    cachedFileSize[getCacheSlot(vbucketId)] = info.fileSize;
+    readHeaderAndUpdateCache(*db, vbucketId);
 
     return true;
 }
@@ -3221,10 +3216,7 @@ couchstore_error_t CouchKVStore::saveDocs(
     }
 
     // retrieve storage system stats for file fragmentation computation
-    const auto info = cb::couchstore::getHeader(*db);
-    cachedSpaceUsed[getCacheSlot(vbid)] = info.spaceUsed;
-    cachedFileSize[getCacheSlot(vbid)] = info.fileSize;
-    cachedDeleteCount[getCacheSlot(vbid)] = info.deletedCount;
+    const auto info = readHeaderAndUpdateCache(*db, vbid);
     cachedOnDiskPrepareSize[getCacheSlot(vbid)] = state.getOnDiskPrepareBytes();
 
     // Check seqno if we wrote documents
@@ -3303,7 +3295,7 @@ CouchKVStore::ReadVBStateResult CouchKVStore::readVBState(Db* db,
     // by couchstore.
     int64_t highSeqno = 0;
     uint64_t purgeSeqno = 0;
-    const auto info = cb::couchstore::getHeader(*db);
+    const auto info = readHeaderAndUpdateCache(*db, vbId);
     highSeqno = info.updateSeqNum;
     purgeSeqno = info.purgeSeqNum;
 
@@ -3433,6 +3425,15 @@ CouchKVStore::ReadVBStateResult CouchKVStore::readVBStateAndUpdateCache(
     return res;
 }
 
+cb::couchstore::Header CouchKVStore::readHeaderAndUpdateCache(Db& db,
+                                                              Vbid vbid) const {
+    auto info = cb::couchstore::getHeader(db);
+    cachedDeleteCount[getCacheSlot(vbid)] = info.deletedCount;
+    cachedFileSize[getCacheSlot(vbid)] = info.fileSize;
+    cachedSpaceUsed[getCacheSlot(vbid)] = info.spaceUsed;
+    return info;
+}
+
 std::string CouchKVStore::makeJsonVBState(const vbucket_state& vbState) {
     nlohmann::json j = vbState;
     // Strip out the high_seqno and purge_seqno - they are automatically
diff --git a/engines/ep/src/kvstore/couch-kvstore/couch-kvstore.h b/engines/ep/src/kvstore/couch-kvstore/couch-kvstore.h
index 8de5060f3..2d6a93c63 100644
--- a/engines/ep/src/kvstore/couch-kvstore/couch-kvstore.h
+++ b/engines/ep/src/kvstore/couch-kvstore/couch-kvstore.h
@@ -823,6 +823,16 @@ protected:
      */
     ReadVBStateResult readVBStateAndUpdateCache(Db* db, Vbid vbid);
 
+    /**
+     * Read the database header and update the cache for the stats available
+     * in the header.
+     *
+     * Note that the header does not include the onDiskPrepareSize (we store
+     * that separately in the vbstate file) and so that stat is not updated
+     * by this function.
+     */
+    cb::couchstore::Header readHeaderAndUpdateCache(Db& db, Vbid vbid) const;
+
     /**
      * Internal getWithHeader that uses Db type for the get
      */
@@ -941,11 +951,16 @@ protected:
      */
     std::unique_ptr<FileOpsInterface> statCollectingFileOpsCompaction;
 
-    /* deleted docs in each file, indexed by vBucket. RelaxedAtomic
-       to allow stats access witout lock */
-    std::vector<cb::RelaxedAtomic<size_t>> cachedDeleteCount;
-    std::vector<cb::RelaxedAtomic<uint64_t>> cachedFileSize;
-    std::vector<cb::RelaxedAtomic<uint64_t>> cachedSpaceUsed;
+    /**
+     * deleted docs in each file, indexed by vBucket. RelaxedAtomic
+     * to allow stats access witout lock. Mutable because we update these from
+     * const members without changing the observable state of the object in a
+     * thread-safe manner.
+     */
+    mutable std::vector<cb::RelaxedAtomic<size_t>> cachedDeleteCount;
+    /// Disk stats cache
+    mutable std::vector<cb::RelaxedAtomic<uint64_t>> cachedFileSize;
+    mutable std::vector<cb::RelaxedAtomic<uint64_t>> cachedSpaceUsed;
     /// Size of on-disk prepares, indexed by vBucket RelaxedAtomic to allow
     /// stats access without lock.
     std::vector<cb::RelaxedAtomic<size_t>> cachedOnDiskPrepareSize;
diff --git a/engines/ep/src/vbucket.cc b/engines/ep/src/vbucket.cc
index 79ddb19c4..007905759 100644
--- a/engines/ep/src/vbucket.cc
+++ b/engines/ep/src/vbucket.cc
@@ -579,6 +579,8 @@ void VBucket::setState_UNLOCKED(
     state = to;
 
     setupSyncReplication(meta ? &meta->at("topology") : nullptr);
+
+    updateStatsForStateChange(oldstate, to);
 }
 
 vbucket_transition_state VBucket::getTransitionState() const {
diff --git a/engines/ep/tests/module_tests/ephemeral_bucket_test.cc b/engines/ep/tests/module_tests/ephemeral_bucket_test.cc
index f63baa9ca..fc7da7ee7 100644
--- a/engines/ep/tests/module_tests/ephemeral_bucket_test.cc
+++ b/engines/ep/tests/module_tests/ephemeral_bucket_test.cc
@@ -239,6 +239,23 @@ TEST_F(EphemeralBucketStatTest, ReplicaMemoryTrackingStateChange) {
 
     EXPECT_EQ(0, stats.replicaHTMemory);
     EXPECT_EQ(0, stats.replicaCheckpointOverhead);
+
+    // Now check what happens when we delete a vBucket, we first need to change
+    // back to replica though to start tracking memory against the replica
+    // counters again.
+    setVBucketStateAndRunPersistTask(vbid, vbucket_state_replica);
+
+    // Check that the replica mem usage has gone up by some amount - not
+    // checking it is an exact value to avoid a brittle test
+    EXPECT_GT(stats.replicaHTMemory, 80);
+    EXPECT_GT(stats.replicaCheckpointOverhead, 80);
+
+    // Deleting a vBucket should also reset the tracked value.
+    EXPECT_EQ(cb::engine_errc::success,
+              engine->deleteVBucket(*cookie, vbid, false));
+
+    EXPECT_EQ(0, stats.replicaHTMemory);
+    EXPECT_EQ(0, stats.replicaCheckpointOverhead);
 }
 
 TEST_F(EphemeralBucketStatTest, ReplicaCheckpointMemoryTracking) {
diff --git a/engines/ep/tests/module_tests/item_pager_test.cc b/engines/ep/tests/module_tests/item_pager_test.cc
index b099edadd..a078e8a28 100644
--- a/engines/ep/tests/module_tests/item_pager_test.cc
+++ b/engines/ep/tests/module_tests/item_pager_test.cc
@@ -1050,7 +1050,8 @@ TEST_P(STItemPagerTest, EvictBGFetchedDeletedItem) {
     checkItemStatePostFetch(false /*expect item to not exist*/);
 }
 
-TEST_P(STItemPagerTest, ReplicaEvictedBeforeActive) {
+// See MB-51958, disabled due to consistent failures
+TEST_P(STItemPagerTest, DISABLED_ReplicaEvictedBeforeActive) {
     // MB-40531 test that the item pager does not evict from active vbuckets
     // if evicting from replicas can reclaim enough memory to reach the low
     // water mark (determined when the item pager first runs).
diff --git a/engines/ep/tests/module_tests/stats_test.cc b/engines/ep/tests/module_tests/stats_test.cc
index c1f47cd43..956316aae 100644
--- a/engines/ep/tests/module_tests/stats_test.cc
+++ b/engines/ep/tests/module_tests/stats_test.cc
@@ -69,6 +69,22 @@ std::map<std::string, std::string> StatTest::get_stat(const char* statkey) {
     return stats;
 }
 
+class ParameterizedStatTest
+    : public StatTest,
+      public ::testing::WithParamInterface<std::string> {
+    void SetUp() override {
+        /**
+         * Items left in the WAL or the memtable do not get counted against the
+         * logical disk size so we force them items into the LSM tree.
+         */
+        config_string +=
+                "magma_checkpoint_interval=0;"
+                "magma_min_checkpoint_interval=0;"
+                "magma_sync_every_batch=true";
+        StatTest::SetUp();
+    }
+};
+
 class DatatypeStatTest : public StatTest,
                          public ::testing::WithParamInterface<std::string> {
 protected:
@@ -732,6 +748,84 @@ TEST_F(StatTest, EngineStatsWarmup) {
     engine->doEngineStats(bucketCollector);
 }
 
+TEST_P(ParameterizedStatTest, DiskInfoStatsAfterWarmup) {
+    using namespace cb::durability;
+    using namespace testing;
+
+    setVBucketState(
+            vbid,
+            vbucket_state_active,
+            {{"topology", nlohmann::json::array({{"active", "replica"}})}});
+
+    // Write some items
+    int num_keys = 2;
+    for (int ii = 0; ii < num_keys; ++ii) {
+        const auto key = makeStoredDocKey(fmt::format("key {}", ii));
+        auto item = make_item(vbid,
+                              key,
+                              {} /*value*/,
+                              0 /*exptime*/,
+                              PROTOCOL_BINARY_RAW_BYTES);
+
+        uint64_t cas = 0;
+        ASSERT_EQ(cb::engine_errc::success,
+                  engine->store(*cookie,
+                                item,
+                                cas,
+                                StoreSemantics::Set,
+                                {},
+                                DocumentState::Alive,
+                                false));
+
+        // Delete some items to bump up the on_disk_deletes
+        if (ii % 2 == 0) {
+            mutation_descr_t mutInfo;
+            ASSERT_EQ(cb::engine_errc::success,
+                      engine->remove(*cookie, key, cas, vbid, {}, mutInfo));
+        }
+    }
+
+    // Write a pending item to bump up ep_db_prepare_size
+    auto pendingKey = makeStoredDocKey("pending");
+    auto pendingItem = makePendingItem(pendingKey, "value");
+    EXPECT_EQ(cb::engine_errc::sync_write_pending,
+              store->set(*pendingItem, cookie));
+
+    dynamic_cast<EPBucket&>(*store).flushVBucket(vbid);
+
+    auto stats = get_stat("diskinfo");
+    EXPECT_LT(0, std::stoi(stats["ep_db_file_size"]));
+    EXPECT_LT(0, std::stoi(stats["ep_db_data_size"]));
+
+    if (!hasMagma()) {
+        // Magma does not track on-disk-prepare-bytes, see MB-42900 for details
+        EXPECT_LT(0, std::stoi(stats["ep_db_prepare_size"]));
+        // Magma always returns 0 from getNumPersistedDeletes
+        auto onDiskDeletes =
+                get_stat(fmt::format("dcp-vbtakeover {}", vbid.get())
+                                   .c_str())["on_disk_deletes"];
+        EXPECT_LT(0, std::stoi(onDiskDeletes));
+    }
+
+    resetEngineAndWarmup();
+
+    // Check the disk stats again after warmup
+    // MB-53829: these would be 0 after warmup
+    auto newStats = get_stat("diskinfo");
+    EXPECT_LT(0, std::stoi(newStats["ep_db_file_size"]));
+    EXPECT_LT(0, std::stoi(newStats["ep_db_data_size"]));
+
+    if (!hasMagma()) {
+        // Magma does not track on-disk-prepare-bytes, see MB-42900 for details
+        EXPECT_LT(0, std::stoi(newStats["ep_db_prepare_size"]));
+        // Magma always returns 0 from getNumPersistedDeletes
+        auto onDiskDeletes =
+                get_stat(fmt::format("dcp-vbtakeover {}", vbid.get())
+                                   .c_str())["on_disk_deletes"];
+        EXPECT_LT(0, std::stoi(onDiskDeletes));
+    }
+}
+
 TEST_P(DatatypeStatTest, datatypesInitiallyZero) {
     // Check that the datatype stats initialise to 0
     auto vals = get_stat(nullptr);
@@ -935,6 +1029,11 @@ TEST_P(DatatypeStatTest, MB23892) {
     setDatatypeItem(store, cookie, PROTOCOL_BINARY_DATATYPE_JSON, "jsonXattrDoc", "[1]");
 }
 
+INSTANTIATE_TEST_SUITE_P(Persistent,
+                         ParameterizedStatTest,
+                         STParameterizedBucketTest::persistentConfigValues(),
+                         STParameterizedBucketTest::PrintToStringParamName);
+
 INSTANTIATE_TEST_SUITE_P(FullAndValueEviction,
                          DatatypeStatTest,
                          ::testing::Values("value_only", "full_eviction"),
diff --git a/tests/testapp/testapp_sasl.cc b/tests/testapp/testapp_sasl.cc
index 5174f39ed..4084df647 100644
--- a/tests/testapp/testapp_sasl.cc
+++ b/tests/testapp/testapp_sasl.cc
@@ -13,6 +13,7 @@
 #include "testapp_client_test.h"
 
 #include <boost/filesystem.hpp>
+#include <cbsasl/client.h>
 #include <algorithm>
 
 using namespace std::string_literals;
@@ -266,3 +267,31 @@ TEST_P(SaslTest, CollectionsConnectionSetup) {
     conn.setAutoRetryTmpfail(auto_retry_tmpfail);
     EXPECT_EQ(cb::mcbp::Status::NotMyVbucket, getRsp.getStatus());
 }
+
+TEST_P(SaslTest, StepWithoutStart) {
+    if (!isSupported("SCRAM-SHA1")) {
+        return;
+    }
+    MemcachedConnection& conn = getConnection();
+    std::string username = "foobar";
+    std::string password = "barbaz";
+    std::string mech = "SCRAM-SHA1";
+    cb::sasl::client::ClientContext client([username] { return username; },
+                                           [password] { return password; },
+                                           mech);
+    auto client_data = client.start();
+
+    if (client_data.first != cb::sasl::Error::OK) {
+        throw std::runtime_error(std::string("cbsasl_client_start (") +
+                                 std::string(client.getName()) +
+                                 std::string("): ") +
+                                 ::to_string(client_data.first));
+    }
+
+    BinprotSaslStepCommand stepCommand;
+    stepCommand.setMechanism(client.getName());
+    stepCommand.setChallenge(client_data.second);
+    auto response = conn.execute(stepCommand);
+
+    EXPECT_FALSE(response.isSuccess());
+}
diff --git a/tests/testapp/testapp_xattr.cc b/tests/testapp/testapp_xattr.cc
index 4761b7cab..9f231a69c 100644
--- a/tests/testapp/testapp_xattr.cc
+++ b/tests/testapp/testapp_xattr.cc
@@ -2214,3 +2214,24 @@ TEST_P(XattrTest, ReplaceBodyWithXattr_WithoutXattrFlag) {
             0});
     ASSERT_EQ(cb::mcbp::Status::Einval, rsp.getStatus());
 }
+
+/// 6.5 introduced a regression where we would return "null" if the document
+/// only contains system xattrs and the connection don't have access
+/// to system xattrs
+TEST_P(XattrTest, MB54776) {
+    setBodyAndXattr(value, {{sysXattr, xattrVal}});
+    userConnection->dropPrivilege(cb::rbac::Privilege::SystemXattrRead);
+
+    BinprotSubdocMultiLookupCommand cmd;
+    cmd.setKey(name);
+    cmd.addGet("$XTOC", SUBDOC_FLAG_XATTR_PATH);
+    cmd.addLookup("", cb::mcbp::ClientOpcode::Get, SUBDOC_FLAG_NONE);
+
+    BinprotSubdocMultiLookupResponse multiResp;
+    multiResp.clear();
+    userConnection->sendCommand(cmd);
+    userConnection->recvResponse(multiResp);
+    EXPECT_EQ(cb::mcbp::Status::Success, multiResp.getStatus());
+    EXPECT_EQ(cb::mcbp::Status::Success, multiResp.getResults()[0].status);
+    EXPECT_EQ("[]", multiResp.getResults()[0].value);
+}
