{"record":{"id":"cffd12b72fda4f87","repo":"tursodatabase/turso","slug":"cursor-id-out-of-bounds","errorCode":null,"errorMessage":"cursor id {} out of bounds","messagePattern":"cursor id (.+?) out of bounds","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/vdbe/execute.rs","lineNumber":1482,"sourceCode":"pub fn op_vopen(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> InsnResult {\n    load_insn!(VOpen { cursor_id }, insn);\n    let (_, cursor_type) = program\n        .cursor_ref\n        .get(*cursor_id)\n        .expect(\"cursor_id should exist in cursor_ref\");\n    let CursorType::VirtualTable(virtual_table) = cursor_type else {\n        panic!(\"VOpen on non-virtual table cursor\");\n    };\n    let cursor = virtual_table.open(program.connection.clone())?;\n    state\n        .cursors\n        .get_mut(*cursor_id)\n        .unwrap_or_else(|| panic!(\"cursor id {} out of bounds\", *cursor_id))\n        .replace(Cursor::Virtual(cursor));\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vcreate(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> InsnResult {\n    load_insn!(\n        VCreate {\n            module_name,\n            table_name,\n            args_reg,\n        },\n        insn","sourceCodeStart":1464,"sourceCodeEnd":1500,"githubUrl":"https://github.com/tursodatabase/turso/blob/492c4a71cd7c2649e7df83da1471b74f4b1c7aa9/core/vdbe/execute.rs#L1464-L1500","documentation":"Inside op_vopen the cursor id exists in program.cursor_ref (the earlier expect passed) but state.cursors has no slot at that index: the runtime cursor array is smaller than the program's cursor table. The ProgramState was not sized for every cursor the program declares, an allocation mismatch between program building and state initialization.","triggerScenarios":"Programs whose cursor count grew after ProgramState was created — subprograms (trigger bodies, co-routines) adding cursors, a resize step skipped when the builder emits more cursors than at state-construction time, or statement reuse against a re-prepared program shape.","commonSituations":"Trigger-heavy schemas; prepared statements cached across schema changes; refactors of cursor accounting in vdbe.","solutions":["Report with the SQL — all cursors must be allocated before execution; likely a missing state.cursors resize for late-added cursors","If embedding, re-prepare statements after schema (DDL) changes instead of reusing cached ones","Move trigger logic into explicit statements to avoid subprogram cursor growth","Upgrade"],"exampleFix":"// contributor fix: size ProgramState from the program's cursor table, not a stale count\nlet mut state = ProgramState::new(program.cursor_ref.len());","handlingStrategy":"validation","validationCode":"// state must cover every cursor the program declares\ndebug_assert!(\n    state.cursors.len() >= program.cursor_ref.len(),\n    \"ProgramState cursor slots must cover program.cursor_ref\"\n);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Re-prepare statements after DDL changes; never reuse cached statements across schema versions","For contributors: assert cursor accounting (cursor_ref length vs state capacity) in debug builds","Add trigger/co-routine conformance tests whenever touching cursor allocation"],"tags":["vdbe","executor","cursor","bounds","state-mismatch","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-14T00:17:10.932Z"}