tursodatabase/turso · critical

parent page should be on the stack

Error message

parent page should be on the stack

What it means

Raised in the DELETE state machine's InteriorNodeReplacement step (core/storage/btree.rs). When the deleted cell is a separator in an interior page, the cursor walks to the predecessor leaf (get_prev_record) and then restores the parent's cell index via stack.node_states.get_mut(btree_depth). The expect fires when the cursor stack has no entry at the interior page's recorded depth.

Source

Thrown at core/storage/btree.rs:7035

                    return_if_io!(self.get_prev_record());

                    let CursorState::Delete(DeleteState::InteriorNodeReplacement {
                        ref page,
                        btree_depth,
                        cell_idx,
                        original_child_pointer,
                        ref mut post_balancing_seek_key,
                        ..
                    }) = self.state
                    else {
                        unreachable!("expected interior node replacement state");
                    };

                    // Ensure we keep the parent page at the same position as before the replacement.
                    self.stack
                        .node_states
                        .get_mut(btree_depth)
                        .expect("parent page should be on the stack")
                        .cell_idx = cell_idx as i32;
                    let (cell_payload, leaf_cell_idx) = {
                        let leaf_page = self.stack.top_ref();
                        let leaf_contents = leaf_page.get_contents();
                        turso_assert!(leaf_contents.is_leaf());
                        turso_assert_greater_than!(leaf_contents.cell_count(), 0);
                        let leaf_cell_idx = leaf_contents.cell_count() - 1;
                        let last_cell_on_child_page =
                            leaf_contents.cell_get(leaf_cell_idx, usable_space)?;

                        let mut cell_payload: crate::alloc::Vec<u8> = crate::alloc::vec![];
                        let child_pointer =
                            original_child_pointer.expect("there should be a pointer");
                        // Rewrite the old leaf cell as an interior cell depending on type.
                        match last_cell_on_child_page {
                            BTreeCell::TableLeafCell(leaf_cell) => {
                                // Table interior cells contain the left child pointer and the rowid as varint.
                                crate::with_btree_allocation_site!(

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Report to Turso with a reproducing statement - a stack-depth invariant break is not caller-fixable
  2. Run PRAGMA integrity_check; corrupted interior pages can break depth bookkeeping
  3. Retry the delete in a new transaction with a freshly prepared statement
  4. As a workaround, delete the row by exact key from a new statement instead of reusing a cursor that has seeked/balanced heavily
Defensive patterns

Strategy: try-catch

Try / catch

let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
    stmt.execute(params![key])
}));
match result {
    Ok(res) => res?,
    Err(_) => { conn.close().ok(); /* reopen and retry once from disk state */ }

Prevention

When it happens

Trigger: DELETE of a row whose key is referenced by an interior (separator) cell, forcing replacement with the predecessor key. The descent to the leaf must keep the interior page pinned at btree_depth; the panic means the stack is shallower than that depth when the parent's cell_idx is restored.

Common situations: Deleting the smallest/oldest keys of a table or index (frequently separators), deep multi-level btrees, deletes interleaved with balancing that changed tree depth, engine regressions in cursor movement during delete.

Related errors


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