apache/cassandra · error · UnsupportedOperationException

Cannot apply a transaction with saveStatus " +…

Error message

Cannot apply a transaction with saveStatus " + command.saveStatus()

What it means

The FORCE_APPLY debug operation on accord_txn_ops can only be run when the target command's saveStatus is in the 'committed but not yet applied/truncated' window: at or after PreApplied is rejected, and at or after TruncatedApplyWithOutcome is rejected. This guard prevents force-applying a transaction whose state is outside the apply path, which would corrupt Accord command state.

Solutions

  1. Check the command's saveStatus first (via the accord debug command table) and only run FORCE_APPLY when it is in [PreApplied, TruncatedApplyWithOutcome).
  2. For commands earlier in the lifecycle, use the appropriate debug op (e.g. FETCH or a commit op) instead of FORCE_APPLY.
  3. For already-truncated commands, use TruncatedApply-related ops rather than plain applyChain.

Example fix

// before
INSERT INTO system_views.accord_txn_ops (txn_id, op) VALUES ('<txnId>', 'FORCE_APPLY'); -- saveStatus = PreCommitted
// after
-- first confirm saveStatus is in range, or advance it first
INSERT INTO system_views.accord_txn_ops (txn_id, op) VALUES ('<txnId>', 'FETCH');
Defensive patterns

Strategy: validation

Validate before calling

// Check the command's saveStatus before issuing FORCE_APPLY
Object status = debugTableLookup("accord_commands", txnId, "save_status");
if (status == null) throw new IllegalStateException("txn unknown");
boolean inRange = compare(status, ">=", "PreApplied") && compare(status, "<", "TruncatedApplyWithOutcome");
if (!inRange) throw new IllegalStateException("FORCE_APPLY not allowed for saveStatus " + status);

Try / catch

// cqlsh / tooling
try {
    session.execute(forceApplyInsert);
} catch (InvalidQueryException | DriverException e) {
    if (e.getMessage().contains("Cannot apply a transaction with saveStatus")) {
        // re-check saveStatus and use the correct lifecycle op
    }
}

Prevention

When it happens

Trigger: INSERTing a FORCE_APPLY row into system_views.accord_txn_ops for a txnId whose current command.saveStatus() is earlier than PreApplied (e.g. PreCommitted/Committed variants below the apply threshold) or already TruncatedApplyWithOutcome or later.

Common situations: Debugging a stuck transaction and force-applying before it has actually been committed; retrying FORCE_APPLY on a txn that has already been truncated by the epilogue; picking a txnId by eye from a debug table without checking its saveStatus.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/java/org/apache/cassandra/db/virtual/AccordDebugKeyspace.java:1816

                            return Commands.applyChain(safeStore, command.asExecuted());
                        Commands.maybeExecute(safeStore, safeCommand, command, true, true, NotifyWaitingOnPlus.adapter(ignore -> {}, true, true));
                        return AsyncChains.success(null);
                    });
                    break;
                case FORCE_UPDATE:
                    run(txnId, commandStoreId, safeStore -> {
                        SafeCommand safeCommand = safeStore.unsafeGet(txnId);
                        safeCommand.update(safeStore, safeCommand.current(), true);
                        return AsyncChains.success(null);
                    });
                    break;
                case FORCE_APPLY:
                    run(txnId, commandStoreId, safeStore -> {
                        SafeCommand safeCommand = safeStore.unsafeGet(txnId);
                        Command command = safeCommand.current();
                        // TODO (expected): we can call applyChain with TruncatedApplyWithOutcome in theory, but the type signature prevents it
                        if (command.saveStatus().compareTo(SaveStatus.PreApplied) < 0 || command.saveStatus().compareTo(SaveStatus.TruncatedApplyWithOutcome) >= 0)
                            throw new UnsupportedOperationException("Cannot apply a transaction with saveStatus " + command.saveStatus());
                        return Commands.applyChain(safeStore, (Command.Executed) command);
                    });
                    break;
                case FETCH:
                    runWithRoute(txnId, commandStoreId, command -> {
                        Timestamp executeAt = command.executeAtIfKnown();
                        return (route, result) -> fetch(txnId, executeAt, route, result);
                    });
                    break;
                case RECOVER:
                    runWithRoute(txnId, commandStoreId, command -> (route, result) -> {
                        recover(txnId, route, result);
                    });
                    break;
                case REQUEUE_PROGRESS_LOG:
                    run(txnId, commandStoreId, safeStore -> {
                        ((DefaultProgressLog)safeStore.progressLog()).requeue(safeStore, TxnStateKind.Waiting, txnId);
                        ((DefaultProgressLog)safeStore.progressLog()).requeue(safeStore, TxnStateKind.Home, txnId);

View on GitHub (pinned to 88fd0f6a0e)