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

  1. Reload the workspace so the transaction state is reloaded or discarded
  2. Check for version mismatch between synced clients and upgrade all clients
  3. 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

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


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)