tursodatabase/turso · critical

post_balancing_seek_key should be Some

Error message

post_balancing_seek_key should be Some

What it means

When a delete decides rebalancing is needed, it takes post_balancing_seek_key - the key saved at delete start (DeleteState::LoadPage carries Some(target_key)) so the cursor can re-position after balancing. post_balancing_seek_key.take().expect("post_balancing_seek_key should be Some") fires when that Option was already consumed or was never set on this path.

Source

Thrown at core/storage/btree.rs:7177

                    }) = self.state
                    else {
                        unreachable!("expected check needs balancing state");
                    };

                    if needs_balancing {
                        let balance_only_ancestor =
                            !leaf_underflows && interior_overflows_or_underflows;
                        if balance_only_ancestor {
                            // Only need to balance the ancestor page; move there immediately.
                            while self.stack.current() > btree_depth {
                                self.stack.pop();
                            }
                        }
                        let balance_both = leaf_underflows && interior_overflows_or_underflows;
                        turso_assert!(matches!(self.balance_state.sub_state, BalanceSubState::Start), "no balancing operation should be in progress during delete", { "sub_state": self.balance_state.sub_state });
                        let post_balancing_seek_key = post_balancing_seek_key
                            .take()
                            .expect("post_balancing_seek_key should be Some");
                        self.save_context(post_balancing_seek_key);
                        self.state = CursorState::Delete(DeleteState::Balancing {
                            balance_ancestor_at_depth: if balance_both {
                                Some(btree_depth)
                            } else {
                                None
                            },
                        });
                    } else {
                        // No balancing needed.
                        if interior_node_was_replaced {
                            // If we did replace an interior node, we need to advance the cursor once to
                            // get back at the interior node that now has the replaced content.
                            // The reason it is important to land here is that the replaced cell was smaller (LT) than the deleted cell,
                            // so we must ensure we skip over it. I.e., when BTreeCursor::next() is called, it will move past the cell
                            // that holds the replaced content.
                            self.state =
                                CursorState::Delete(DeleteState::PostInteriorNodeReplacement);

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Report to Turso with the statement sequence - double-take or missing key in the delete state machine
  2. Retry with a freshly prepared DELETE statement and cursor
  3. Run PRAGMA integrity_check after the panic to confirm the tree is intact
  4. Upgrade the engine version
Defensive patterns

Strategy: try-catch

Try / catch

let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
    conn.execute("DELETE FROM t WHERE rowid = ?", [rowid])
}));
if result.is_err() { conn.close().ok(); /* reopen and retry once */ }

Prevention

When it happens

Trigger: A delete whose leaf or interior page under/overflows (triggering DeleteState::Balancing) after the seek key was already taken by an earlier state transition, or a delete initiated without a target key so the Option was None from the start.

Common situations: Repeated deletes reusing one cursor where a duplicate take() races IO re-entry, deletes driven by UPDATE on indexed columns, engine regressions in DeleteState field movement (post_balancing_seek_key.take() chains).

Related errors


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20). Data as JSON: /api/errors/f96093dfdc3a6d5b. Report an issue: GitHub.