siyuan-note/siyuan · error · TxErr
block swap undo state is unavailable
Error message
block swap undo state is unavailable
What it means
During transaction replay (undo/redo), the kernel needs the stored blockSwapState snapshot attached to the swap operation to reconstruct the original trees. This error means the replay is attempting a swap operation whose undo state is missing. It indicates internal state loss rather than a user input problem.
Solutions
- Reload the workspace so the transaction state is reloaded or discarded
- Check for version mismatch between synced clients and upgrade all clients
- If reproducible, report a bug: replay state should always be attached by doSwapBlockRef
Defensive patterns
Strategy: try-catch
Validate before calling
if (op.action === 'swap-block-ref' && op.isReplay && op.blockSwapState == null) {
// cannot replay; fall back to re-executing or skipping
} Type guard
function hasSwapState(op) { return op != null && op.blockSwapState != null; } Try / catch
try { await post('/api/transactions', tx); } catch (e) {
if (e.message.includes('block swap undo state is unavailable')) { /* skip or re-execute op */ }
} Prevention
- Do not strip custom operation metadata when serializing transactions
- Keep all sync clients on a kernel version that supports block swap state
When it happens
Trigger: Replaying (undo/redo) a transaction whose swap-block-ref operation lacks operation.blockSwapState; state persisted before this feature existed being replayed by a newer kernel.
Common situations: Workspace data or sync snapshots from older versions replayed after an upgrade; corrupted transaction journal; custom code stripping operation metadata before replay.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- block changed since conversion
- block swap must be submitted as a separate transaction
- block swap notebook has changed
- cannot replay block swap across encrypted notebook…
- block operation context
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/c2add30510fe8582.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/transaction_block_swap.go:40
before, after []blockSwapFragment
rootIDs []string
}
type blockSwapFragment struct {
rootID, boxID, previousID, nextID string
node *ast.Node
}
func (tx *Transaction) doSwapBlockRef(operation *Operation) *TxErr {
fail := func(err error) *TxErr {
return &TxErr{code: TxErrCodePushMsg, id: operation.ID, msg: err.Error()}
}
if len(tx.DoOperations) != 1 {
return fail(errors.New("block swap must be submitted as a separate transaction"))
}
if tx.isReplay {
if operation.blockSwapState == nil {
return fail(errors.New("block swap undo state is unavailable"))
}
if err := tx.replayBlockSwap(operation); err != nil {
return fail(err)
}
return nil
}
data, err := json.Marshal(operation.Data)
if err != nil {
return fail(err)
}
var options blockSwapOptions
if err = json.Unmarshal(data, &options); err != nil || options.IncludeChildren == nil || options.OriginalToEmbed == nil {
return fail(errors.New("invalid block swap options"))
}
refTree, err := tx.loadTree(operation.ID)
if err != nil {
return fail(err)
}View on GitHub (pinned to 9f775e8a12)