siyuan-note/siyuan · error · TxErr
block swap must be submitted as a separate transaction
Error message
block swap must be submitted as a separate transaction
What it means
This error means a block swap operation was submitted in a transaction together with other operations. The kernel requires that a 'swap-block-ref' operation be the sole operation in its transaction, because the swap manipulates two whole trees and stores an undo state snapshot that only makes sense atomically. doSwapBlockRef rejects any transaction whose DoOperations slice length is not exactly 1.
Solutions
- Submit the swap-block-ref operation alone in its own transaction with exactly one entry in the do array
- Move any other operations into separate transaction calls before or after the swap
- If batching is required, perform the swap last, in a dedicated transaction
Example fix
// before
POST /api/transactions
{"transactions":[{"doOperations":[{"action":"insert","data":"..."},{"action":"swap-block-ref",...}],"undoOperations":[]}]}
// after
POST /api/transactions
{"transactions":[{"doOperations":[{"action":"swap-block-ref",...}],"undoOperations":[]}]}
// then a second call for the insert Defensive patterns
Strategy: validation
Validate before calling
const isSwapOnly = tx.doOperations.length === 1 && tx.doOperations[0].action === 'swap-block-ref';
if (!isSwapOnly) throw new Error('swap-block-ref must be submitted as the only operation in its transaction'); Prevention
- Always submit swap-block-ref in a dedicated single-operation transaction
- Wrap swap submission in a helper that asserts doOperations.length === 1
- Never merge swap operations into batched transaction payloads
When it happens
Trigger: Calling the transactions API with a 'swap-block-ref' operation bundled alongside other do/undo operations in the same transaction payload; performing a multi-operation batch that includes a block swap.
Common situations: Plugins or scripts batching several editor mutations into one transaction for efficiency; frontend code refactoring that merges a swap into an existing transaction array; replay/undo pipelines that concatenate queued operations.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- block swap requires two non-document blocks
- block swap undo state is unavailable
- invalid block swap options
- Access to encrypted notebook data is not supported via this…
- AI editor action must not be empty
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/265e9529e3b94c58.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/transaction_block_swap.go:36
}
// 撤销快照仅保存在内核内存中,客户端不能提交或替换快照。
type blockSwapState struct {
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"))
}View on GitHub (pinned to 9f775e8a12)