{"record":{"id":"39ef1367c5d6c936","repo":"sinelaw/fresh","slug":"active-window-present-buffer-close","errorCode":null,"errorMessage":"active window present","messagePattern":"active window present","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-editor/src/app/buffer_close.rs","lineNumber":40,"sourceCode":"    /// Buffer the host split's `active_buffer` becomes.\n    buffer: BufferId,\n    /// `true` when no other buffer existed and a fresh empty one was created.\n    created_empty: bool,\n    /// Set when the LRU landing target was a buffer *group* rather than a\n    /// buffer: `buffer` is then only housekeeping and the caller re-activates\n    /// the group tab on this leaf.\n    return_to_group: Option<LeafId>,\n}\n\nimpl Editor {\n    /// Close the given buffer\n    pub fn close_buffer(&mut self, id: BufferId) -> anyhow::Result<()> {\n        // Check for unsaved changes\n        if let Some(state) = self\n            .windows\n            .get(&self.active_window)\n            .map(|w| &w.buffers)\n            .expect(\"active window present\")\n            .get(&id)\n        {\n            if state.buffer.is_modified() {\n                return Err(anyhow::anyhow!(\"Buffer has unsaved changes\"));\n            }\n        }\n        self.close_buffer_internal(id, false)\n    }\n\n    /// Force close the given buffer without checking for unsaved changes\n    /// Use this when the user has already confirmed they want to discard changes\n    pub fn force_close_buffer(&mut self, id: BufferId) -> anyhow::Result<()> {\n        self.close_buffer_internal(id, false)\n    }\n\n    /// Close the last editor tab while a Utility Dock is present, keeping the\n    /// editor leaf alive but empty (issue #2283). The editor leaf we intend to\n    /// keep is made the active split so the synthesized placeholder buffer","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/src/app/buffer_close.rs#L22-L58","documentation":"Panic from `.expect(\"active window present\")` at the top of the public `close_buffer` (buffer_close.rs:40). Before closing, it checks the target buffer for unsaved changes by looking it up through the active window; the expect asserts the active window exists. It panics when `close_buffer` is called (e.g. from `close_tab_in_split`, `preview_file`, `dismiss_preview`, terminal close) while `active_window` no longer resolves in `self.windows`.","triggerScenarios":"Any caller (`close_tab_in_split`, `close_tab_in_split_silent`, `preview_file`, `dismiss_preview`, `handle_close_buffer`, `handle_close_buffer`-family `handle_close_terminal`) invokes `close_buffer(id)` after the active window was removed from `self.windows` without updating `self.active_window`.","commonSituations":"Clicking a tab close button (issue-#1620-adjacent UI paths) during window teardown; scripted keybindings closing buffers after the window is gone; preview dismissal racing window close.","solutions":["Guard the lookup: if the active window is missing, treat the buffer as absent and proceed with the close (or return early) instead of panicking.","Fix the window-close path to keep `active_window` valid at all times.","Route tab-close UI events through a check that the owning window still exists before calling `close_buffer`.","Write a test closing a buffer while the active window is stale to lock in non-panicking behavior."],"exampleFix":"// before\n.map(|w| &w.buffers)\n.expect(\"active window present\")\n.get(&id)\n// after\nlet has_unsaved = self\n    .windows\n    .get(&self.active_window)\n    .and_then(|w| w.buffers.get(&id))\n    .map(|state| state.buffer.is_modified())\n    .unwrap_or(false);\nif has_unsaved {\n    return Err(anyhow::anyhow!(\"Buffer has unsaved changes\"));\n}","handlingStrategy":"validation","validationCode":"// before calling close_buffer:\nif !app.windows.contains_key(&app.active_window) {\n    // window already gone; buffer close is a no-op or should be routed elsewhere\n    return;\n}","typeGuard":"fn buffer_in_active_window(app: &App, id: BufferId) -> bool {\n    app.windows.get(&app.active_window)\n        .map(|w| w.buffers.contains_key(&id))\n        .unwrap_or(false)\n}","tryCatchPattern":"match std::panic::catch_unwind(AssertUnwindSafe(|| app.close_buffer(id))) {\n    Ok(res) => res?,\n    Err(_) => { /* treat as buffer already gone */ }\n}","preventionTips":["Make close_buffer tolerate a missing active window (unwrap_or semantics) since its result only depends on the target buffer.","Verify the owning window exists before dispatching tab-close UI events.","Keep active_window valid across every window removal.","Add a regression test for the issue #1620 family of click-then-panic paths."],"tags":["rust","panic","buffer-management","internal-invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}