{"record":{"id":"1b44974cc3ad2a52","repo":"tursodatabase/turso","slug":"cursor-id-cursor-id-out-of-bounds","errorCode":null,"errorMessage":"cursor id {cursor_id} out of bounds","messagePattern":"cursor id (.+?) out of bounds","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/vdbe/mod.rs","lineNumber":1471,"sourceCode":"            }\n            crate::statement::StatementStatusCounter::Sort => self.metrics.sort_operations = 0,\n            crate::statement::StatementStatusCounter::VmStep => self.metrics.insn_executed = 0,\n            crate::statement::StatementStatusCounter::Reprepare => self.metrics.reprepares = 0,\n            crate::statement::StatementStatusCounter::RowsRead => self.metrics.rows_read = 0,\n            crate::statement::StatementStatusCounter::RowsWritten => self.metrics.rows_written = 0,\n        }\n        if let Some(OpProgramState::Step { statement, .. }) = self.active_op_state.program_mut() {\n            statement.reset_stmt_status(counter);\n        }\n        for statement in self.subprogram_stmt_cache.values_mut() {\n            statement.reset_stmt_status(counter);\n        }\n    }\n\n    pub fn get_cursor(&mut self, cursor_id: CursorID) -> &mut Cursor {\n        self.cursors\n            .get_mut(cursor_id)\n            .unwrap_or_else(|| panic!(\"cursor id {cursor_id} out of bounds\"))\n            .as_mut()\n            .unwrap_or_else(|| panic!(\"cursor id {cursor_id} is None\"))\n    }\n\n    /// Close all virtual table cursors owned by this program.\n    ///\n    /// A virtual table cursor can own a nested helper statement on the same\n    /// connection (e.g. `PragmaVirtualTableCursor` runs `PRAGMA ...` via\n    /// `Connection::prepare_internal`), and that helper holds the\n    /// connection's nested-statement guard until it is dropped. Both\n    /// `commit_txn` and `abort` consult `Connection::is_nested_stmt()` to\n    /// decide whether the current statement owns top-level transaction\n    /// finalization, so the helpers must be dropped first — otherwise a root\n    /// statement that scanned a pragma virtual table misclassifies itself as\n    /// nested, skips ending its implicit read transaction, and subsequent\n    /// writes on the connection never auto-commit (issue #7466).\n    pub(crate) fn close_virtual_table_cursors(&mut self) {\n        for slot in self.cursors.iter_mut() {","sourceCodeStart":1453,"sourceCodeEnd":1489,"githubUrl":"https://github.com/tursodatabase/turso/blob/492c4a71cd7c2649e7df83da1471b74f4b1c7aa9/core/vdbe/mod.rs#L1453-L1489","documentation":"ProgramState::get_cursor() panicked because cursor_id does not index into the per-execution cursors vector. The instruction stream referenced a cursor slot the state never allocated — a program/state sizing mismatch or bytecode using an id that was never opened. It is the runtime out-of-bounds form of a bad cursor reference.","triggerScenarios":"Any opcode calling state.get_cursor(id) with id >= cursors.len() — after cursor renumbering, subprograms adding cursors after ProgramState creation, or a jump into code whose Open cursor emission was skipped.","commonSituations":"Trigger bodies and co-routines; cached prepared statements reused after schema changes; refactors of cursor allocation in vdbe/translate.","solutions":["Report with SQL — the engine must guarantee cursor slots cover every reference in the program","Re-prepare statements after schema/DDL changes instead of reusing cached ones","Simplify triggers/co-routines in the failing statement to change cursor layout","Upgrade"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// debug audit: every cursor id referenced by an insn must be within the state's cursor array\nfn audit_cursor_bounds(program: &Program, state: &ProgramState) -> Result<()> {\n    let len = state.cursors.len();\n    for id in program.referenced_cursor_ids() {\n        if id >= len {\n            crate::bail_parse_error!(\"cursor {id} out of bounds (state has {len} slots)\");\n        }\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Re-prepare statements after DDL changes","Assert cursor accounting (cursor_ref length vs state slots) in debug builds","Catch panics per-statement at the embedding boundary"],"tags":["vdbe","state","cursor","bounds","panic","runtime"],"backgroundTag":"cursor-id-out-of-bounds","analyzedSha":"492c4a71cd7c2649e7df83da1471b74f4b1c7aa9","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}