{"record":{"id":"4f67340d393f6635","repo":"gfx-rs/wgpu","slug":"mismatched-pop-error-scope-call-error-scopes-must","errorCode":null,"errorMessage":"Mismatched pop_error_scope call: error scopes must be popped in reverse order.","messagePattern":"Mismatched pop_error_scope call: error scopes must be popped in reverse order\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"wgpu-core/src/error.rs","lineNumber":405,"sourceCode":"        // and we are supposed to just drop the error scope on the floor.\n        let is_panicking = is_panicking();\n        let thread_id = thread_id::ThreadId::current();\n        let err = \"Mismatched pop_error_scope call: no error scope for this thread. Error scopes are thread-local.\";\n        let scopes = match error_sink.scopes.get_mut(&thread_id) {\n            Some(s) => s,\n            None => {\n                if !is_panicking {\n                    panic!(\"{err}\");\n                } else {\n                    return None;\n                }\n            }\n        };\n        if scopes.is_empty() && !is_panicking {\n            panic!(\"{err}\");\n        }\n        if index as usize != scopes.len() - 1 && !is_panicking {\n            panic!(\n                \"Mismatched pop_error_scope call: error scopes must be popped in reverse order.\"\n            );\n        }\n\n        // It would be more correct in this case to use `remove` here so that when unwinding is occurring\n        // we would remove the correct error scope, but we don't have such a primitive on the web\n        // and having consistent behavior here is more important. If you are unwinding and it unwinds\n        // the guards in the wrong order, it's totally reasonable to have incorrect behavior.\n        let scope = match scopes.pop() {\n            Some(s) => s,\n            None if !is_panicking => unreachable!(),\n            None => return None,\n        };\n\n        scope.error\n    }\n}\n","sourceCodeStart":387,"sourceCodeEnd":423,"githubUrl":"https://github.com/gfx-rs/wgpu/blob/3e11ff59bf3f9795d285ecc045014089640d7248/wgpu-core/src/error.rs#L387-L423","documentation":"Error scopes must be popped in LIFO (reverse) order. This panic fires when pop_error_scope is given/encounters an index that is not the top of the thread's scope stack, i.e. an inner scope is still open while an outer one is being popped.","triggerScenarios":"Popping an outer scope while an inner push_error_scope is still open; interleaving scope lifetimes across tasks so scopes overlap incorrectly; popping scopes out of nesting order in nested validation regions.","commonSituations":"Nested error-scope usage (scope inside scope) where the inner pop was forgotten or deferred; concurrent async tasks each opening scopes on the same thread executor and interleaving pushes/pops; helper functions that close the wrong scope.","solutions":["Restructure so scopes are strictly nested: always pop the most recently pushed scope first.","Close all inner scopes (await their pops) before popping the outer scope.","Avoid sharing a thread's scope stack across interleaved async tasks; keep push/pop within a single async block.","Flatten to one scope level per function to make LIFO order trivially correct."],"exampleFix":"// before\ndevice.push_error_scope(outer);\ndevice.push_error_scope(inner);\ndevice.pop_error_scope().await; // pops inner? index mismatch panics if popping outer\n// after\ndevice.push_error_scope(outer);\ndevice.push_error_scope(inner);\ndevice.pop_error_scope().await; // inner first\ndevice.pop_error_scope().await; // then outer","handlingStrategy":"validation","validationCode":"// enforce LIFO with a scope stack abstraction\nlet guard = ScopeGuard::push(&device, ErrorFilter::Validation)?; // inner scopes as nested guards\n// popping happens automatically in drop order = LIFO","typeGuard":"struct ScopeGuard<'a> { device: &'a Device, done: bool }\nimpl<'a> ScopeGuard<'a> {\n    fn inner(&'a self, k: ErrorFilter) -> ScopeGuard<'a> {\n        self.device.push_error_scope(k);\n        ScopeGuard { device: self.device, done: false }\n    }\n}\nimpl Drop for ScopeGuard<'_> { fn drop(&mut self) { /* pop via runtime */ } }","tryCatchPattern":null,"preventionTips":["Prefer one scope level per function; avoid manual nested pops.","Use drop-order (RAII) to guarantee LIFO scope closure.","Never interleave scope lifetimes across concurrently scheduled tasks on one thread."],"tags":["wgpu","panic","error-scope","lifo-order"],"backgroundTag":"mismatched-error-scope","analyzedSha":"3e11ff59bf3f9795d285ecc045014089640d7248","analyzedAt":"2026-09-03T01:43:21.459Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}