{"record":{"id":"a9e992f97ac52b4f","repo":"tursodatabase/turso","slug":"parent-page-should-be-on-the-stack","errorCode":null,"errorMessage":"parent page should be on the stack","messagePattern":"parent page should be on the stack","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"core/storage/btree.rs","lineNumber":7035,"sourceCode":"                    return_if_io!(self.get_prev_record());\n\n                    let CursorState::Delete(DeleteState::InteriorNodeReplacement {\n                        ref page,\n                        btree_depth,\n                        cell_idx,\n                        original_child_pointer,\n                        ref mut post_balancing_seek_key,\n                        ..\n                    }) = self.state\n                    else {\n                        unreachable!(\"expected interior node replacement state\");\n                    };\n\n                    // Ensure we keep the parent page at the same position as before the replacement.\n                    self.stack\n                        .node_states\n                        .get_mut(btree_depth)\n                        .expect(\"parent page should be on the stack\")\n                        .cell_idx = cell_idx as i32;\n                    let (cell_payload, leaf_cell_idx) = {\n                        let leaf_page = self.stack.top_ref();\n                        let leaf_contents = leaf_page.get_contents();\n                        turso_assert!(leaf_contents.is_leaf());\n                        turso_assert_greater_than!(leaf_contents.cell_count(), 0);\n                        let leaf_cell_idx = leaf_contents.cell_count() - 1;\n                        let last_cell_on_child_page =\n                            leaf_contents.cell_get(leaf_cell_idx, usable_space)?;\n\n                        let mut cell_payload: crate::alloc::Vec<u8> = crate::alloc::vec![];\n                        let child_pointer =\n                            original_child_pointer.expect(\"there should be a pointer\");\n                        // Rewrite the old leaf cell as an interior cell depending on type.\n                        match last_cell_on_child_page {\n                            BTreeCell::TableLeafCell(leaf_cell) => {\n                                // Table interior cells contain the left child pointer and the rowid as varint.\n                                crate::with_btree_allocation_site!(","sourceCodeStart":7017,"sourceCodeEnd":7053,"githubUrl":"https://github.com/tursodatabase/turso/blob/492c4a71cd7c2649e7df83da1471b74f4b1c7aa9/core/storage/btree.rs#L7017-L7053","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Report to Turso with a reproducing statement - a stack-depth invariant break is not caller-fixable","Run PRAGMA integrity_check; corrupted interior pages can break depth bookkeeping","Retry the delete in a new transaction with a freshly prepared statement","As a workaround, delete the row by exact key from a new statement instead of reusing a cursor that has seeked/balanced heavily"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let result = std::panic::catch_unwind(AssertUnwindSafe(|| {\n    stmt.execute(params![key])\n}));\nmatch result {\n    Ok(res) => res?,\n    Err(_) => { conn.close().ok(); /* reopen and retry once from disk state */ }","preventionTips":["Use freshly prepared statements for destructive operations instead of long-lived heavily-seeked cursors","Run PRAGMA integrity_check periodically to catch tree damage before deletes trip invariants","Exercise delete-heavy workloads (including smallest-key deletes) in staging before engine upgrades","Report reproducible cases upstream - these panics mark engine bugs"],"tags":["btree","delete","cursor","stack-invariant","panic"],"backgroundTag":"invalid-cursor-state","analyzedSha":"492c4a71cd7c2649e7df83da1471b74f4b1c7aa9","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}