{"record":{"id":"e719f2ac52a77e50","repo":"nushell/nushell","slug":"internal-error-missing-required-scope-frame","errorCode":null,"errorMessage":"internal error: missing required scope frame","messagePattern":"internal error: missing required scope frame","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/nu-protocol/src/engine/state_delta.rs","lineNumber":87,"sourceCode":"        self.vars.len()\n    }\n\n    pub fn num_decls(&self) -> usize {\n        self.decls.len()\n    }\n\n    pub fn num_blocks(&self) -> usize {\n        self.blocks.len()\n    }\n\n    pub fn num_modules(&self) -> usize {\n        self.modules.len()\n    }\n\n    pub fn last_scope_frame_mut(&mut self) -> &mut ScopeFrame {\n        self.scope\n            .last_mut()\n            .expect(\"internal error: missing required scope frame\")\n    }\n\n    pub fn last_scope_frame(&self) -> &ScopeFrame {\n        self.scope\n            .last()\n            .expect(\"internal error: missing required scope frame\")\n    }\n\n    pub fn last_overlay_mut(&mut self) -> Option<&mut OverlayFrame> {\n        let last_scope = self\n            .scope\n            .last_mut()\n            .expect(\"internal error: missing required scope frame\");\n\n        if let Some(last_overlay_id) = last_scope.active_overlays.last() {\n            Some(\n                &mut last_scope\n                    .overlays","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/nushell/nushell/blob/8e03210652f3c48c4521cec982d96e4cb6c67181/crates/nu-protocol/src/engine/state_delta.rs#L69-L105","documentation":"StateDelta maintains a stack of ScopeFrames; StateDelta::new() always pushes one frame and enter_scope/exit_scope push and pop it. last_scope_frame_mut() asserts the stack is non-empty before returning the top frame. A panic means the delta's scope stack was emptied — exit_scope was called more often than enter_scope, an engine-state bookkeeping bug rather than a script error.","triggerScenarios":"An unbalanced enter_scope/exit_scope sequence (parser or engine code path that pops in both success and error branches), followed by any scope lookup: find_decl/find_overlay, variable resolution, merge into permanent state.","commonSituations":"Patched parsers or embedders driving StateWorkingSet scopes manually; regressions after upgrading nu-protocol/nu-parser; custom repl/eval loops that call exit_scope on error paths that never entered.","solutions":["Audit custom code that calls working_set.enter_scope()/exit_scope() and make them strictly balanced (enter once, exit once per path)","Use the standard parsing/evaluation entry points instead of driving scope frames directly","Pin the last known-good nu-protocol/nu-parser pair and report the regression upstream with the script that triggers it"],"exampleFix":"// before: error path exits a scope it never entered\nworking_set.exit_scope();\nif err { working_set.exit_scope(); return; }\n\n// after: one enter pairs with one exit on every path\nworking_set.enter_scope();\nlet result = do_scoped(working_set);\nworking_set.exit_scope();","handlingStrategy":"validation","validationCode":"// StateWorkingSet::delta is accessible in-crate; guard scope stack depth\n// before engine operations if you drive scopes manually.\n// (state.delta.scope starts with exactly one frame)\nassert!(working_set.delta.scope.len() >= 1, \"scope stack over-popped\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pair every enter_scope() with exactly one exit_scope() on all paths, including errors","Create a fresh StateWorkingSet per parse/eval unit instead of manually unwinding scopes","Add debug_asserts on scope depth after custom error-handling paths"],"tags":["rust","panic","nu-protocol","engine-state","scope-frame","internal-invariant"],"backgroundTag":"internal-invariant-panic","analyzedSha":"8e03210652f3c48c4521cec982d96e4cb6c67181","analyzedAt":"2026-08-17T16:24:07.527Z","contentChangedAt":"2026-08-17T16:24:07.527Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}