Commit e5972fb
committed
🤖 Fix workspace path resolution for legacy workspaces
Use config.findWorkspace() to get actual workspace path instead of computing it.
This fixes ENOENT errors for legacy workspaces where the directory name doesn't
match the computed path.
**Root Cause:**
- Legacy workspaces: ID is 'cmux-compact-flag-bug', dir is 'compact-flag-bug'
- getWorkspacePath(projectPath, id) computed: src/cmux/cmux-compact-flag-bug ❌
- Actual path in config: src/cmux/compact-flag-bug ✅
**Solution:**
- Use findWorkspace() which returns path directly from config
- Config stores actual filesystem paths (source of truth)
- Works for both legacy and new workspace formats
**Changes:**
- WORKSPACE_EXECUTE_BASH: Use findWorkspace() instead of getWorkspacePath()
- Removed debug logging from getWorkspaceMetadata and ipcMain handler
- Removed projectPath validation (no longer needed since using config path)
Fixes git status ENOENT errors.
🤖 Fix all workspace path resolution + reduce git status error spam
**Fixes path resolution everywhere:**
- aiService.sendMessage: Use findWorkspace() for AI tool operations
- ipcMain.removeWorkspaceInternal: Use findWorkspace() for deletion
- ipcMain.enrichMetadataWithPaths: Use findWorkspace() for frontend paths
**Reduces console spam:**
- GitStatusStore: Only log OUTPUT TRUNCATED/OVERFLOW as debug level
- Common in large repos, not an actionable error
**Root cause:**
getWorkspacePath() computes paths that don't match legacy workspace directories:
- Computed: ~/.cmux/src/cmux/cmux-compact-flag-bug
- Actual: ~/.cmux/src/cmux/compact-flag-bug
**Solution:**
Always use findWorkspace() to get actual paths from config (source of truth).
Only use getWorkspacePath() for NEW workspace creation.
**Changes:**
- 3 more call sites fixed to use findWorkspace()
- Added warning comment to getWorkspacePath()
- GitStatusStore filters out truncation spam
🤖 Replace ambiguous workspacePath with explicit namedWorkspacePath
Replaced all uses of workspacePath with namedWorkspacePath throughout the codebase
to be explicit that we're using the user-friendly path (symlink for new workspaces).
**Benefits:**
- Clear distinction between stableWorkspacePath (for operations) and namedWorkspacePath (for display)
- No more ambiguity about which path is being used
- Window title now shows workspace name instead of ID
- Terminal opens to named path (user-friendly)
- Rename updates UI immediately with enriched metadata
**Files changed:**
- WorkspaceSelection interface: workspacePath → namedWorkspacePath
- AIView props: workspacePath → namedWorkspacePath
- App.tsx: Window title uses workspace name, all selections use namedWorkspacePath
- All command palette sources updated
- All hooks updated
This anti-pattern cleanup ensures clarity everywhere paths are used.
🤖 Add safety checks for undefined namedWorkspacePath
Handle case where selectedWorkspace has old format without namedWorkspacePath
(from localStorage before rebuild). Uses optional chaining and fallback to workspaceId.
Fixes: Cannot read properties of undefined (reading 'split')
🤖 Subscribe to workspace metadata updates in useWorkspaceManagement
Fixed rename not updating UI immediately by subscribing to WORKSPACE_METADATA
IPC events. Previously only reloaded metadata after explicit operations.
**Root cause:**
- workspaceMetadata was only loaded on mount and after create/delete/rename
- But rename emits WORKSPACE_METADATA event that wasn't being listened to
- So metadata map in App.tsx was stale until manual reload
**Solution:**
- Subscribe to workspace.subscribeToMetadata() in useEffect
- Update workspaceMetadata map when event received
- Unsubscribe on cleanup
**Result:**
- Rename updates UI immediately (sidebar, window title, paths)
- No manual refresh needed
🤖 Fix type annotation for metadata event
🤖 Fix: Use onMetadata not subscribeToMetadata
🤖 Add debug logging for workspace metadata loading
🤖 Fix: onMetadata already sends all initial state, remove duplicate load
onMetadata is designed to send all current metadata immediately upon subscription,
so we don't need the manual loadWorkspaceMetadata() call. The duplicate load was
causing a race condition where one could overwrite the other.
Removed loadWorkspaceMetadata() from useEffect and rely solely on onMetadata
subscription for both initial state and updates.
Fix: Use workspace ID for metadata lookup, not path
The sortedWorkspacesByProject was building a path-based lookup map,
but workspaceMetadata is keyed by workspace ID. This caused all
workspaces to be filtered out when building the sorted list.
Now we directly look up by ws.id from the config.
Remove debug logging from useWorkspaceManagement
Fix: Detect workspace renames in sortedWorkspacesByProject
The comparison function only checked workspace IDs, so when a
workspace was renamed (ID stays same, name changes), it didn't
detect the change and the UI didn't update.
Now checks both id and name to properly detect renames.
Batch workspace metadata updates on initial load
When subscribing to metadata, backend sends one event per workspace
synchronously. This was causing N re-renders for N workspaces.
Now we batch these updates using queueMicrotask - all synchronous
events are collected and flushed in a single state update after the
current task completes. This reduces 19 re-renders to 1 on startup.
Fix: Restore workspace from URL hash after metadata loads
The restore effect ran on mount before workspaceMetadata was loaded,
so it always failed to find the workspace. Now it waits for metadata
to be loaded and only restores once.
Use named workspace paths for all user-facing operations
Agent bash calls and UI bash execute should use the friendly named
workspace path (symlink or legacy dir name), not the internal stable
ID path. This matches what users see in the UI.
Backwards compatible: legacy workspaces have no symlinks, so the
named path IS the actual directory path.
Fix: Update old localStorage entries with missing namedWorkspacePath
Old selectedWorkspace entries from localStorage may be missing the
namedWorkspacePath field. The validation effect now detects this and
updates the workspace with current metadata, preventing errors in
command palette and other components that expect the field.
Fix crash on workspace delete and hash restore priority
- Add null check in workspace sort comparison to prevent crash when workspace is deleted
- Fix hash restore: now takes priority over localStorage by running once on mount
- Add guard to prevent duplicate 'Updating workspace' log messages
Fix critical bug: findWorkspace now checks config.id first
Root cause: findWorkspace() was reading metadata.json files instead of
checking workspace.id from config, causing all workspaces to fail enrichment.
Changes:
- findWorkspace() now checks workspace.id from config first (primary source)
- Falls back to metadata.json only for unmigrated legacy workspaces
- Remove metadata.json writes from workspace create/rename (config is source of truth)
- Keep metadata.json read for backward compat during migration
This fixes:
- "Workspace no longer exists" errors
- Title showing ID instead of name
- Terminal opening in project path instead of workspace path
All caused by enrichMetadataWithPaths() failing when findWorkspace() returned null.
Simplify: getAllWorkspaceMetadata returns complete data with paths
Architectural simplification to eliminate O(n²) complexity and prevent bugs:
BEFORE:
- getAllWorkspaceMetadata() returned WorkspaceMetadata (no paths)
- Subscription handler sent incomplete data to frontend → UI broke
- enrichMetadataWithPaths() had to search config again to find paths
- Each caller had to remember to call enrichment → easy to forget
AFTER:
- getAllWorkspaceMetadata() returns FrontendWorkspaceMetadata with paths
- Paths computed once during the initial loop (we already have the data!)
- No enrichment step needed - data is always complete
- Subscription handler sends complete data → frontend always gets paths
Changes:
- Added addPathsToMetadata() helper to avoid duplication (DRY)
- Updated all 5 path-adding locations to use helper
- Removed enrichMetadataWithPaths() (~20 LOC deleted)
- Updated all callers to use complete metadata directly
Net result: -45 LOC, O(n) instead of O(n²), impossible to forget enrichment
Add debug logging for metadata subscription
To diagnose why workspaces aren't loading on reload
Simplify metadata loading with proper loading state
BEFORE:
- Subscription sent metadata piece-by-piece on subscribe
- Batching logic to avoid re-renders
- Race condition: restore effect ran before batch completed
- Checked workspaceMetadata.size === 0 which passed, then validation cleared selection
AFTER:
- Call workspace.list() once on mount to get all metadata
- Set loading: true until complete
- All effects wait for metadataLoading === false
- Subscription only used for updates (create/rename/delete)
Eliminates race conditions and simplifies the loading flow.
Fix: Use namedWorkspacePath for terminal in command palette
Bug: Command palette 'Open Workspace in Terminal' was passing workspace ID
instead of path, causing terminal to open in wrong directory.
Fix:
- Changed field name from 'workspacePath' to 'workspaceId' (matches actual value)
- Look up workspace metadata to get namedWorkspacePath
- Pass namedWorkspacePath to openTerminal (user-friendly symlink path)
Unify terminal opening: always use workspace ID, not paths
BEFORE:
- AIView: Called openTerminal(namedWorkspacePath) directly
- Command palette 'Open Current': Called callback(namedWorkspacePath)
- Command palette 'Open Any': Called callback(workspaceId) then looked up path
- App.tsx callback: Took workspacePath parameter
Multiple code paths, easy to make mistakes with paths.
AFTER:
- All callers pass workspace ID to openWorkspaceInTerminal()
- Single code path in App.tsx looks up metadata and extracts namedWorkspacePath
- Consistent: workspace ID is the universal identifier
This eliminates the risk of passing wrong paths (stable vs named).
🤖 Fix macOS terminal opening and clean up legacy code
- Fix: macOS Ghostty now called directly with --working-directory flag
Previously used 'open -a Ghostty /path' which doesn't set cwd
- Remove unused legacy functions from gitService.ts:
- createWorktree (duplicate, real one in git.ts)
- moveWorktree (unused)
- listWorktrees (unused)
- isGitRepository (unused)
- getMainWorktreeFromWorktree (duplicate)
- Update gitService.test.ts to import createWorktree from git.ts
- Add logging for successful terminal opens
Revert terminal opening changes - original code was correct
The 'open -a AppName /directory' command DOES set the cwd correctly.
My 'fix' broke it by calling ghostty CLI directly which spawns a background
process instead of opening a GUI window.
🤖 Use cwd option instead of passing directory as arg to terminal
Set cwd in spawn options rather than passing workspacePath as argument.
This is cleaner and more consistent with Linux terminal handling.
🤖 Fix macOS terminal opening with proper --args syntax
Problem: Terminals weren't opening at all on macOS
Root cause: Using cwd option doesn't work with 'open' command
Solution:
- Ghostty: Use 'open -a Ghostty --args --working-directory=$path'
The --args flag passes remaining arguments to the app itself
- Terminal.app: Pass directory path directly (original approach works)
Verified manually that both approaches open terminal in correct directory.
🤖 macOS: match main for Ghostty terminal opens (avoid regression)
Revert Ghostty invocation to exactly mirror main: use
open -a Ghostty <workspacePath>
No flags or --args. This avoids any behavior change on macOS and
fixes the regression where Ghostty windows didn’t appear.
Generated with .
🤖 Log full terminal command invocation in backend
Add log.info() before spawning terminal processes on all platforms:
- macOS: logs 'open -a Ghostty <path>' or 'open -a Terminal <path>'
- Windows: logs 'cmd /c start cmd /K cd /D <path>'
- Linux: logs '<terminal> <args>' with optional cwd info
This helps debug terminal launching issues by showing exactly what
command is being executed.
Generated with `cmux`.1 parent 6ff0ab1 commit e5972fb
File tree
11 files changed
+248
-307
lines changed- src
- components
- hooks
- services
- utils/commands
11 files changed
+248
-307
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
166 | 166 | | |
167 | 167 | | |
168 | 168 | | |
169 | | - | |
| 169 | + | |
170 | 170 | | |
171 | 171 | | |
172 | 172 | | |
| |||
209 | 209 | | |
210 | 210 | | |
211 | 211 | | |
212 | | - | |
213 | | - | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
214 | 216 | | |
215 | 217 | | |
216 | 218 | | |
| |||
222 | 224 | | |
223 | 225 | | |
224 | 226 | | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
225 | 230 | | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
226 | 237 | | |
227 | 238 | | |
228 | 239 | | |
229 | 240 | | |
230 | 241 | | |
231 | | - | |
| 242 | + | |
232 | 243 | | |
233 | 244 | | |
234 | | - | |
| 245 | + | |
235 | 246 | | |
236 | 247 | | |
237 | 248 | | |
238 | 249 | | |
239 | | - | |
| 250 | + | |
240 | 251 | | |
241 | 252 | | |
242 | 253 | | |
243 | | - | |
244 | | - | |
245 | | - | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
246 | 257 | | |
247 | | - | |
| 258 | + | |
248 | 259 | | |
249 | | - | |
250 | | - | |
251 | | - | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
252 | 268 | | |
253 | 269 | | |
254 | 270 | | |
255 | 271 | | |
256 | | - | |
257 | 272 | | |
258 | 273 | | |
259 | 274 | | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
260 | 286 | | |
261 | 287 | | |
262 | | - | |
| 288 | + | |
263 | 289 | | |
264 | | - | |
265 | | - | |
266 | | - | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
267 | 297 | | |
268 | 298 | | |
269 | 299 | | |
| |||
335 | 365 | | |
336 | 366 | | |
337 | 367 | | |
338 | | - | |
339 | | - | |
340 | | - | |
341 | | - | |
342 | | - | |
343 | | - | |
344 | | - | |
345 | 368 | | |
346 | 369 | | |
347 | | - | |
| 370 | + | |
348 | 371 | | |
349 | | - | |
| 372 | + | |
350 | 373 | | |
351 | 374 | | |
352 | 375 | | |
| |||
361 | 384 | | |
362 | 385 | | |
363 | 386 | | |
364 | | - | |
| 387 | + | |
365 | 388 | | |
366 | 389 | | |
367 | 390 | | |
368 | | - | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
369 | 397 | | |
370 | 398 | | |
371 | 399 | | |
| |||
403 | 431 | | |
404 | 432 | | |
405 | 433 | | |
406 | | - | |
| 434 | + | |
407 | 435 | | |
408 | 436 | | |
409 | 437 | | |
| |||
506 | 534 | | |
507 | 535 | | |
508 | 536 | | |
509 | | - | |
510 | | - | |
511 | | - | |
512 | | - | |
513 | | - | |
514 | | - | |
| 537 | + | |
515 | 538 | | |
516 | 539 | | |
517 | 540 | | |
| |||
653 | 676 | | |
654 | 677 | | |
655 | 678 | | |
656 | | - | |
| 679 | + | |
657 | 680 | | |
658 | 681 | | |
659 | 682 | | |
660 | 683 | | |
661 | 684 | | |
662 | | - | |
663 | | - | |
| 685 | + | |
| 686 | + | |
664 | 687 | | |
665 | 688 | | |
666 | 689 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
193 | 193 | | |
194 | 194 | | |
195 | 195 | | |
196 | | - | |
| 196 | + | |
197 | 197 | | |
198 | 198 | | |
199 | 199 | | |
200 | 200 | | |
201 | 201 | | |
202 | 202 | | |
203 | 203 | | |
204 | | - | |
| 204 | + | |
205 | 205 | | |
206 | 206 | | |
207 | 207 | | |
| |||
311 | 311 | | |
312 | 312 | | |
313 | 313 | | |
314 | | - | |
315 | | - | |
| 314 | + | |
| 315 | + | |
316 | 316 | | |
317 | 317 | | |
318 | 318 | | |
| |||
443 | 443 | | |
444 | 444 | | |
445 | 445 | | |
446 | | - | |
| 446 | + | |
447 | 447 | | |
448 | 448 | | |
449 | 449 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
123 | 123 | | |
124 | 124 | | |
125 | 125 | | |
126 | | - | |
| 126 | + | |
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
153 | | - | |
| 153 | + | |
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
| |||
246 | 246 | | |
247 | 247 | | |
248 | 248 | | |
249 | | - | |
| 249 | + | |
250 | 250 | | |
251 | 251 | | |
252 | 252 | | |
| |||
256 | 256 | | |
257 | 257 | | |
258 | 258 | | |
259 | | - | |
| 259 | + | |
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
263 | 263 | | |
264 | 264 | | |
265 | 265 | | |
266 | 266 | | |
267 | | - | |
| 267 | + | |
268 | 268 | | |
269 | 269 | | |
270 | 270 | | |
| |||
0 commit comments