tursodatabase/turso · critical
there should be a pointer
Error message
there should be a pointer
What it means
Same InteriorNodeReplacement step: rebuilding the predecessor leaf cell as an interior cell needs original_child_pointer, the left_child_page captured when the interior cell was parsed in DeleteState::FindCell. original_child_pointer.expect("there should be a pointer") fires when that recorded value is None even though the path requires an interior cell.
Source
Thrown at core/storage/btree.rs:7048
// 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!(
CellPayload,
cell_payload.try_extend(child_pointer.to_be_bytes())
)?;
write_varint_to_vec(leaf_cell.rowid as u64, &mut cell_payload)?;
}
BTreeCell::IndexLeafCell(leaf_cell) => {
// Index interior cells contain:
// 1. The left child pointer
// 2. The payload size as varint
// 3. The payload
// 4. The first overflow page as varint, omitted if no overflow.
crate::with_btree_allocation_site!(
CellPayload,View on GitHub (pinned to 492c4a71cd)
Solutions
- Report to Turso with the failing DELETE and database - state-plumbing bug or corruption
- Run PRAGMA integrity_check on the database
- Retry the delete in a fresh transaction; single-statement repros usually succeed on retry
- Update to the latest engine version if the delete path was recently touched
Defensive patterns
Strategy: try-catch
Try / catch
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
conn.execute("DELETE FROM t WHERE id = ?", [id])
}));
if result.is_err() {
conn.close().ok(); // engine state suspect; reopen from disk
} Prevention
- Verify database integrity before delete campaigns on critical data
- Keep engine version current - delete/interior-cell replacement is actively maintained
- Wrap destructive statements in explicit transactions so a retry boundary is well-defined
- Retain backups; panics of this class indicate engine bugs or corruption, both worth investigating
When it happens
Trigger: Deleting through an interior cell where FindCell recorded original_child_pointer = None (cell parsed as a leaf cell on the first pass but as interior later), or state carried across IO re-entry from a non-interior delete.
Common situations: Deletes hitting interior cells after IO yields re-enter the state machine, corrupted pages whose cell types changed mid-transaction, engine regressions in DeleteState field plumbing.
Related errors
- invalid cell payload
- parent page should be on the stack
- ancestor page should be on the stack
- post_balancing_seek_key should be Some
- has_record=true but record() returned None
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/1c61fc80b9f4fc86.
Report an issue: GitHub.