apache/cassandra · error · IllegalStateException

Cluster is not running unsafe TCM mode, can't revert epoch

Error message

Cluster is not running unsafe TCM mode, can't revert epoch

What it means

Thrown by CMSOperations.unsafeRevertClusterMetadata when reverting cluster metadata to an earlier epoch is requested while the unsafe TCM mode flag is disabled. Unsafe mode is an explicit opt-in that permits destructive metadata rollback operations.

Solutions

  1. Set unsafe_tcm_mode: true in cassandra.yaml and restart the node, then retry the revert.
  2. Confirm with the Cassandra documentation that metadata rollback is required before enabling the flag.
  3. After the revert completes, disable unsafe_tcm_mode again.

Example fix

// before (cassandra.yaml)
# unsafe_tcm_mode not set
// after
unsafe_tcm_mode: true
Defensive patterns

Strategy: validation

Validate before calling

if (!DatabaseDescriptor.getUnsafeTCMMode()) throw new IllegalStateException("Enable unsafe_tcm_mode and restart before reverting epochs");

Try / catch

try { ops.unsafeRevertClusterMetadata(epoch); } catch (IllegalStateException e) { logger.error("Revert rejected: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Calling unsafeRevertClusterMetadata(long epoch) (e.g. via nodetool) when DatabaseDescriptor.getUnsafeTCMMode() returns false.

Common situations: Operator tries to roll back metadata after a failed upgrade without having enabled unsafe_tcm_mode in cassandra.yaml, or forgets to restart the node after setting it.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/9c3581f111ca9550. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/tcm/CMSOperations.java:268

        info.put(COMMITS_PAUSED, Boolean.toString(cms.commitsPaused()));
        info.put(REPLICATION_FACTOR, metadata.epoch.isBefore(Epoch.FIRST) ? "" : ReplicationParams.meta(metadata).toString());
        info.put(CMS_ID, Integer.toString(metadata.metadataIdentifier));
        return info;
    }

    @Override
    public void snapshotClusterMetadata()
    {
        logger.info("Triggering cluster metadata snapshot");
        Epoch epoch = cms.triggerSnapshot().epoch;
        logger.info("Cluster metadata snapshot triggered at {}", epoch);
    }

    @Override
    public void unsafeRevertClusterMetadata(long epoch)
    {
        if (!DatabaseDescriptor.getUnsafeTCMMode())
            throw new IllegalStateException("Cluster is not running unsafe TCM mode, can't revert epoch");
        cms.revertToEpoch(Epoch.create(epoch));
    }

    @Override
    public String dumpClusterMetadata(long epoch, long transformToEpoch, String version) throws IOException
    {
        return cms.dumpClusterMetadata(Epoch.create(epoch), Epoch.create(transformToEpoch), Version.valueOf(version));
    }

    @Override
    public String dumpClusterMetadata() throws IOException
    {
        return dumpClusterMetadata(Epoch.EMPTY.getEpoch(),
                                   ClusterMetadata.current().epoch.getEpoch() + 1000,
                                   NodeVersion.CURRENT.serializationVersion().toString());
    }

    @Override

View on GitHub (pinned to 88fd0f6a0e)