diff --git a/src/browser/App.tsx b/src/browser/App.tsx
index f7fb5498bb..20874b4c3b 100644
--- a/src/browser/App.tsx
+++ b/src/browser/App.tsx
@@ -121,14 +121,6 @@ function AppInner() {
prevWorkspaceRef.current = selectedWorkspace;
}, [selectedWorkspace, telemetry]);
- // Validate selectedWorkspace when metadata changes
- // Clear selection if workspace was deleted
- useEffect(() => {
- if (selectedWorkspace && !workspaceMetadata.has(selectedWorkspace.workspaceId)) {
- setSelectedWorkspace(null);
- }
- }, [selectedWorkspace, workspaceMetadata, setSelectedWorkspace]);
-
// Track last-read timestamps for unread indicators
const { lastReadTimestamps, onToggleUnread } = useUnreadTracking(selectedWorkspace);
@@ -571,15 +563,22 @@ function AppInner() {
{selectedWorkspace ? (
(() => {
const currentMetadata = workspaceMetadata.get(selectedWorkspace.workspaceId);
+ // Guard: Don't render AIView if workspace metadata not found.
+ // This can happen when selectedWorkspace (from localStorage) refers to a
+ // deleted workspace, or during a race condition on reload before the
+ // validation effect clears the stale selection.
+ if (!currentMetadata) {
+ return null;
+ }
// Use metadata.name for workspace name (works for both worktree and local runtimes)
// Fallback to path-based derivation for legacy compatibility
const workspaceName =
- currentMetadata?.name ??
+ currentMetadata.name ??
selectedWorkspace.namedWorkspacePath?.split("/").pop() ??
selectedWorkspace.workspaceId;
// Use live metadata path (updates on rename) with fallback to initial path
const workspacePath =
- currentMetadata?.namedWorkspacePath ?? selectedWorkspace.namedWorkspacePath ?? "";
+ currentMetadata.namedWorkspacePath ?? selectedWorkspace.namedWorkspacePath ?? "";
return (
);
diff --git a/src/browser/contexts/WorkspaceContext.tsx b/src/browser/contexts/WorkspaceContext.tsx
index 293308db24..b62990427d 100644
--- a/src/browser/contexts/WorkspaceContext.tsx
+++ b/src/browser/contexts/WorkspaceContext.tsx
@@ -118,7 +118,7 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
);
const loadWorkspaceMetadata = useCallback(async () => {
- if (!api) return;
+ if (!api) return false; // Return false to indicate metadata wasn't loaded
try {
const metadataList = await api.workspace.list(undefined);
const metadataMap = new Map();
@@ -128,16 +128,22 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
metadataMap.set(metadata.id, metadata);
}
setWorkspaceMetadata(metadataMap);
+ return true; // Return true to indicate metadata was loaded
} catch (error) {
console.error("Failed to load workspace metadata:", error);
setWorkspaceMetadata(new Map());
+ return true; // Still return true - we tried to load, just got empty result
}
}, [setWorkspaceMetadata, api]);
- // Load metadata once on mount
+ // Load metadata once on mount (and again when api becomes available)
useEffect(() => {
void (async () => {
- await loadWorkspaceMetadata();
+ const loaded = await loadWorkspaceMetadata();
+ if (!loaded) {
+ // api not available yet - effect will run again when api connects
+ return;
+ }
// After loading metadata (which may trigger migration), reload projects
// to ensure frontend has the updated config with workspace IDs
await refreshProjects();