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
- Check the command's saveStatus first (via the accord debug command table) and only run FORCE_APPLY when it is in [PreApplied, TruncatedApplyWithOutcome).
- For commands earlier in the lifecycle, use the appropriate debug op (e.g. FETCH or a commit op) instead of FORCE_APPLY.
- 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
- Always read the command's saveStatus from the accord debug tables before FORCE_APPLY.
- Understand the SaveStatus ordering: only the window [PreApplied, TruncatedApplyWithOutcome) is applyable.
- For truncated commands use the truncated-apply ops, not FORCE_APPLY.
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
- txnId + " not known"
- accord.cache_size option was set incorrectly to
- accord.journal_directory must be specified
- accord.journal_directory must not be the same as any…
- accord.journal_directory must not be the same as the…
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)