From ef0b4ba37ae8280c5040c637d0fb708fe7b57f82 Mon Sep 17 00:00:00 2001 From: Nathan Esquenazi Date: Sun, 21 Jun 2026 23:31:48 -0700 Subject: [PATCH] fix(#4662): clear workspace skeleton on switch-failure no-workspace path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The success path clears a stranded workspace-tree skeleton when the new profile has no bound workspace (both switch branches), but the catch block only restored the tree `if (S.session && S.session.workspace)` — with no else. So a profile-switch failure while the workspace panel is open and the (still-current) previous session has no workspace left the up-front skeleton shimmering forever: the same skeleton-strand class the Codex/Opus gates targeted, in the one path they didn't cover. Mirror the success-path no-workspace handling in the failure path. Adds a static regression assertion that the catch block clears the workspace skeleton. Co-Authored-By: Claude Opus 4.8 (1M context) --- static/panels.js | 5 +++++ .../test_issue4662_profile_switch_skeleton_static.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/static/panels.js b/static/panels.js index 922ad211..6b6d2f78 100644 --- a/static/panels.js +++ b/static/panels.js @@ -6113,6 +6113,11 @@ async function switchToProfile(name) { } if (_workspaceVisibleAtStart && S.session && S.session.workspace && typeof loadDir === 'function') { loadDir('.'); + } else if (_workspaceVisibleAtStart && typeof clearWorkspaceTreeSkeleton === 'function') { + // No workspace to restore on the (still-current) previous profile — + // clear the up-front workspace skeleton so it doesn't strand on a switch + // failure, mirroring the success-path no-workspace handling (#4662). + clearWorkspaceTreeSkeleton(); } } } finally { diff --git a/tests/test_issue4662_profile_switch_skeleton_static.py b/tests/test_issue4662_profile_switch_skeleton_static.py index 1d2619be..b955652c 100644 --- a/tests/test_issue4662_profile_switch_skeleton_static.py +++ b/tests/test_issue4662_profile_switch_skeleton_static.py @@ -58,6 +58,17 @@ class TestSwitchWiring: catch = body[body.index("} catch (e) {"):] assert "renderSessionListFromCache()" in catch, "failed switch must restore real list" + def test_failure_path_clears_workspace_skeleton_when_no_workspace(self): + # The success path clears a stranded workspace skeleton when the profile + # has no bound workspace; the failure path must do the same — otherwise a + # switch failure while the workspace panel is open and the (still-current) + # session has no workspace strands the up-front skeleton forever (#4662). + body = _switch_body() + catch = body[body.index("} catch (e) {"):] + assert "clearWorkspaceTreeSkeleton()" in catch, ( + "failed switch must clear the workspace skeleton when there's no workspace to restore" + ) + def test_noop_self_switch_early_returns(self): # Opus gate #4662: a switch to the already-active profile must bail before # showing a skeleton (activateCurrentProfile() doesn't pre-check), else it