Skip to content

Commit 95df44f

Browse files
committed
🤖 fix: delete workspace by ID not path, fix title bar for local runtime
Bug fixes: - Delete workspace filters by ID instead of path. For local project-dir runtimes, all workspaces share the same path (project dir), so filtering by path was deleting ALL local workspaces when one was deleted. - WorkspaceHeader now uses metadata.name for display, fixing 'project/project' title issue for local runtimes where namedWorkspacePath is the project path. Docs: - Split runtime docs into docs/runtime/{local,worktree,ssh}.md - Add docs/runtime/index.md overview with comparison table - Update cross-references in workspaces.md and vscode-extension.md Tests: - Add integration tests for local project-dir workspace deletion - Verify only specified workspace is deleted, not all with same path - Verify project directory is not deleted
1 parent 5cfecf1 commit 95df44f

File tree

11 files changed

+242
-57
lines changed

11 files changed

+242
-57
lines changed

docs/SUMMARY.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
# Features
1010

1111
- [Workspaces](./workspaces.md)
12-
- [Local](./local.md)
13-
- [SSH](./ssh.md)
12+
- [Runtimes](./runtime/index.md)
13+
- [Local](./runtime/local.md)
14+
- [Worktree](./runtime/worktree.md)
15+
- [SSH](./runtime/ssh.md)
1416
- [Forking](./fork.md)
1517
- [Init Hooks](./init-hooks.md)
1618
- [VS Code Extension](./vscode-extension.md)

docs/local.md

Lines changed: 0 additions & 22 deletions
This file was deleted.

docs/runtime/index.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Runtimes
2+
3+
Runtimes determine where and how mux executes agent workspaces.
4+
5+
| Runtime | Isolation | Best For |
6+
| --------------------------------------- | ------------------------------------------ | ---------------------------------------- |
7+
| **[Worktree](./worktree.md)** (default) | Each workspace gets its own directory | Running multiple agents in parallel |
8+
| **[Local](./local.md)** | All workspaces share the project directory | Quick edits to your working copy |
9+
| **[SSH](./ssh.md)** | Remote execution over SSH | Security, performance, heavy parallelism |
10+
11+
## Choosing a Runtime
12+
13+
When creating a workspace, select the runtime from the dropdown in the workspace creation UI.
14+
15+
## Init Hooks
16+
17+
[Init hooks](../init-hooks.md) can detect the runtime type via the `MUX_RUNTIME` environment variable:
18+
19+
- `worktree` — Worktree runtime
20+
- `local` — Local runtime
21+
- `ssh` — SSH runtime
22+
23+
This lets your init hook adapt behavior, e.g., skip worktree-specific setup when running in local mode.

docs/runtime/local.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Local Runtime
2+
3+
Local runtime runs the agent directly in your project directory—the same directory you use for development. There's no isolation between workspaces.
4+
5+
## When to Use
6+
7+
- **Quick one-off tasks** in your current working copy
8+
- **Reviewing agent work** alongside your own uncommitted changes
9+
- **Projects where worktrees don't work well** (e.g., some monorepos)
10+
11+
## Caveats
12+
13+
⚠️ **No isolation**: Multiple local workspaces for the same project see and modify the same files. Running them simultaneously can cause conflicts.
14+
15+
⚠️ **Affects your working copy**: Agent changes happen in your actual project directory, not a separate worktree.
16+
17+
When multiple local workspaces exist for the same project, mux shows a warning if another workspace is actively streaming.
18+
19+
## Filesystem
20+
21+
The workspace path is your project directory itself. No additional directories are created.
File renamed without changes.

docs/runtime/worktree.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Worktree Runtime
2+
3+
Worktree runtime (the default) uses [git worktrees](https://git-scm.com/docs/git-worktree) to isolate each workspace. Each workspace gets its own directory with independent working changes and checkout state.
4+
5+
## How Worktrees Work
6+
7+
A worktree is a separate directory that shares `.git` with the main repository but has independent working changes. **All committed changes from any worktree are visible to all other worktrees including the main repository.**
8+
9+
A **worktree is not locked to a branch**. The agent can switch branches, enter detached HEAD state, etc. When you create a workspace, the agent starts at the selected branch but may switch freely. **Define your agent's branching strategy in AGENTS.md**
10+
11+
## Filesystem Layout
12+
13+
Worktree workspaces are stored in `~/.mux/src/<project-name>/<workspace-name>`:
14+
15+
```
16+
~/.mux/src/
17+
my-project/
18+
feature-auth/ # workspace 1
19+
fix-ci-flakes/ # workspace 2
20+
```
21+
22+
## When to Use
23+
24+
- **Running multiple agents in parallel** (recommended)
25+
- **Keeping your working copy clean** while agents work
26+
- **Most projects** — this is the default for good reason

docs/vscode-extension.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,5 @@ repository.
8585
## Related
8686

8787
- [Workspaces Overview](./workspaces.md)
88-
- [SSH Workspaces](./ssh.md)
88+
- [SSH Runtime](./runtime/ssh.md)
8989
- [VS Code Remote-SSH Documentation](https://code.visualstudio.com/docs/remote/ssh)

docs/workspaces.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@
22

33
Workspaces in mux provide isolated development environments for parallel agent work. Each workspace maintains its own Git state, allowing you to explore different approaches, run multiple tasks simultaneously, or test changes without affecting your main repository.
44

5-
## Workspace Types
5+
## Runtimes
66

7-
mux supports two workspace backends:
7+
mux supports three [runtime types](./runtime/index.md):
88

9-
- **[Local Workspaces](./local.md)**: Use [git worktrees](https://git-scm.com/docs/git-worktree) on your local machine. Worktrees share the `.git` directory with your main repository while maintaining independent working changes.
9+
- **[Worktree](./runtime/worktree.md)** (default): Isolated directories using [git worktrees](https://git-scm.com/docs/git-worktree). Worktrees share `.git` with your main repository while maintaining independent working changes.
1010

11-
- **[SSH Workspaces](./ssh.md)**: Regular git clones on a remote server accessed via SSH. These are completely independent repositories stored on the remote machine.
11+
- **[Local](./runtime/local.md)**: Run directly in your project directory. No isolation—best for quick edits to your working copy.
1212

13-
## Choosing a Backend
13+
- **[SSH](./runtime/ssh.md)**: Remote execution over SSH. Ideal for heavy workloads, security isolation, or leveraging remote infrastructure.
1414

15-
The workspace backend is selected when you create a workspace:
15+
## Choosing a Runtime
1616

17-
- **Local**: Best for fast iteration, local testing, and when you want to leverage your local machine's resources
18-
- **SSH**: Ideal for heavy workloads, long-running tasks, or when you need access to remote infrastructure
17+
The runtime is selected when you create a workspace:
18+
19+
- **Worktree** (default): Best for parallel agent work with isolation
20+
- **Local**: Quick tasks in your current working copy
21+
- **SSH**: Heavy workloads, security, or remote infrastructure
1922

2023
## Key Concepts
2124

src/browser/App.tsx

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -559,27 +559,31 @@ function AppInner() {
559559
<div className="mobile-main-content flex min-w-0 flex-1 flex-col overflow-hidden">
560560
<div className="mobile-layout flex flex-1 overflow-hidden">
561561
{selectedWorkspace ? (
562-
<ErrorBoundary
563-
workspaceInfo={`${selectedWorkspace.projectName}/${selectedWorkspace.namedWorkspacePath?.split("/").pop() ?? selectedWorkspace.workspaceId}`}
564-
>
565-
<AIView
566-
key={selectedWorkspace.workspaceId}
567-
workspaceId={selectedWorkspace.workspaceId}
568-
projectPath={selectedWorkspace.projectPath}
569-
projectName={selectedWorkspace.projectName}
570-
branch={
571-
selectedWorkspace.namedWorkspacePath?.split("/").pop() ??
572-
selectedWorkspace.workspaceId
573-
}
574-
namedWorkspacePath={selectedWorkspace.namedWorkspacePath ?? ""}
575-
runtimeConfig={
576-
workspaceMetadata.get(selectedWorkspace.workspaceId)?.runtimeConfig
577-
}
578-
incompatibleRuntime={
579-
workspaceMetadata.get(selectedWorkspace.workspaceId)?.incompatibleRuntime
580-
}
581-
/>
582-
</ErrorBoundary>
562+
(() => {
563+
const currentMetadata = workspaceMetadata.get(selectedWorkspace.workspaceId);
564+
// Use metadata.name for workspace name (works for both worktree and local runtimes)
565+
// Fallback to path-based derivation for legacy compatibility
566+
const workspaceName =
567+
currentMetadata?.name ??
568+
selectedWorkspace.namedWorkspacePath?.split("/").pop() ??
569+
selectedWorkspace.workspaceId;
570+
return (
571+
<ErrorBoundary
572+
workspaceInfo={`${selectedWorkspace.projectName}/${workspaceName}`}
573+
>
574+
<AIView
575+
key={selectedWorkspace.workspaceId}
576+
workspaceId={selectedWorkspace.workspaceId}
577+
projectPath={selectedWorkspace.projectPath}
578+
projectName={selectedWorkspace.projectName}
579+
branch={workspaceName}
580+
namedWorkspacePath={selectedWorkspace.namedWorkspacePath ?? ""}
581+
runtimeConfig={currentMetadata?.runtimeConfig}
582+
incompatibleRuntime={currentMetadata?.incompatibleRuntime}
583+
/>
584+
</ErrorBoundary>
585+
);
586+
})()
583587
) : creationProjectPath ? (
584588
(() => {
585589
const projectPath = creationProjectPath;

src/node/services/ipcMain.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,7 +1512,7 @@ export class IpcMain {
15121512
log.info(`Workspace ${workspaceId} metadata exists but not found in config`);
15131513
return { success: true }; // Consider it already removed
15141514
}
1515-
const { projectPath, workspacePath } = workspace;
1515+
const { projectPath, workspacePath: _workspacePath } = workspace;
15161516

15171517
// Create runtime instance for this workspace
15181518
// For local runtimes, workdir should be srcDir, not the individual workspace path
@@ -1538,12 +1538,14 @@ export class IpcMain {
15381538
// Delete workspace metadata (fire and forget)
15391539
void this.extensionMetadata.deleteWorkspace(workspaceId);
15401540

1541-
// Update config to remove the workspace from all projects
1541+
// Update config to remove the workspace by ID
1542+
// NOTE: Filter by ID, not path. For local project-dir runtimes, multiple workspaces
1543+
// share the same path (the project directory), so filtering by path would delete them all.
15421544
const projectsConfig = this.config.loadConfigOrDefault();
15431545
let configUpdated = false;
15441546
for (const [_projectPath, projectConfig] of projectsConfig.projects.entries()) {
15451547
const initialCount = projectConfig.workspaces.length;
1546-
projectConfig.workspaces = projectConfig.workspaces.filter((w) => w.path !== workspacePath);
1548+
projectConfig.workspaces = projectConfig.workspaces.filter((w) => w.id !== workspaceId);
15471549
if (projectConfig.workspaces.length < initialCount) {
15481550
configUpdated = true;
15491551
}

0 commit comments

Comments
 (0)