Skip to content

🤖 feat: add multi-project workspace support - #2759

Merged
ThomasK33 merged 80 commits into
mainfrom
workspace-yw8w
Mar 11, 2026
Merged

ThomasK33 merged 80 commits into
mainfrom
workspace-yw8w

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Adds VS Code-style multi-root workspace support to Mux. A user can create a single workspace spanning multiple projects (e.g., mux + mux-md). The agent gets one cwd — a container directory with symlinks to per-project worktrees — and works across all projects in a single session. Each project retains its own git identity.

Background

Multi-project workflows are common — an agent fixing a library and its consumer simultaneously, updating docs alongside source code, etc. Today each project gets its own workspace, forcing users to coordinate across separate sessions. This PR enables a single session to span multiple repos.

Implementation

Architecture

  • Container directory: ~/.mux/src/_workspaces/<name>/ contains symlinks (mux/ → worktree, mux-md/ → worktree)
  • Multi-project is an orchestration layer on top of existing per-project runtimes — runtimes themselves don't change
  • All projects share the same RuntimeConfig (mixed runtimes deferred)

New/Changed Components (12 commits)

Phase Component Key Files
1 Data Model — ProjectRefSchema, projects field on metadata, isMultiProject()/getProjects() helpers src/common/orpc/schemas/workspace.ts, src/common/utils/multiProject.ts
2 Container Manager — creates/removes symlink container directories src/node/multiProject/containerManager.ts
3 MultiProjectRuntime — wraps per-project runtimes, delegates lifecycle + exec src/node/runtime/multiProjectRuntime.ts
4 Workspace Lifecycle — createMultiProject(), extended remove()/rename(), IPC route src/node/services/workspaceService.ts
5 Git Root Discovery — discoverGitRoots() for multi-root workspaces src/node/multiProject/gitRootDiscovery.ts
6 Symlink Fix — FileChangeTracker canonicalizes paths via realpath() src/node/services/utils/fileChangeTracker.ts
7 Sub-Agent Support — fork orchestrator creates per-project child worktrees + containers src/node/services/utils/forkOrchestrator.ts
8 Agent Context — bash tool description lists project directories src/node/services/tools/bash.ts
9 UI — "Multi-Project" sidebar section, command palette action, creation modal src/browser/components/ProjectSidebar/, MultiProjectWorkspaceCreateModal/
10 Refactor — centralized MULTI_PROJECT_CONFIG_KEY constant src/common/constants/multiProject.ts

Config Storage

Multi-project workspaces are stored under a synthetic "_multi" key in config.json:

{
  "projects": {
    "/path/to/mux": { "workspaces": [...] },
    "_multi": {
      "workspaces": [{
        "id": "abc1234567",
        "name": "my-feature",
        "projects": [
          { "projectPath": "/path/to/mux", "projectName": "mux" },
          { "projectPath": "/path/to/mux-md", "projectName": "mux-md" }
        ]
      }]
    }
  }
}

Validation

  • 29 new tests across 6 test files — all pass
  • 116 existing regression tests verified — no regressions
  • make static-check passes clean (typecheck + lint + formatting + docs links)

Risks

  • Config "_multi" key: new convention that config loading/filtering must handle. Existing workspaces unaffected (the projects field is optional).
  • Git status polling: N scripts instead of 1 per multi-project workspace. Mitigated by staggering/focus-based polling (deferred optimization).
  • Scattered git calls: ~6-8 files need multi-root updates for full git integration; this PR includes the discoverGitRoots() utility but integration into gitPatchArtifactService and gitStatus is deferred.

📋 Implementation Plan

Phase 1 — Data Model & Helpers

  • ProjectRefSchema with projectPath + projectName
  • Optional projects array on WorkspaceMetadataSchema and WorkspaceConfigSchema
  • isMultiProject() and getProjects() utility functions

Phase 2 — Container Directory Management

  • ContainerManager class: createContainer(), removeContainer(), getContainerPath()
  • Containers live at {srcBaseDir}/_workspaces/{workspaceName}/
  • Symlinks to per-project workspace directories

Phase 3 — MultiProjectRuntime Wrapper

  • Implements Runtime interface
  • Delegates lifecycle to per-project runtimes
  • Uses container path as cwd for exec

Phase 4 — Workspace Lifecycle

  • createMultiProject() on WorkspaceService with rollback on partial failure
  • Extended remove() to clean up all per-project workspaces + container
  • Extended rename() to rename all per-project workspaces + recreate container
  • IPC route workspace.createMultiProject

Phase 5 — Git Multi-Root Awareness

  • discoverGitRoots() — scans workspace for .git dirs in children

Phase 6 — FileChangeTracker Symlink Fix

  • realpath() canonicalization for file path storage

Phase 7 — Sub-Agent Support

  • Extended orchestrateFork for multi-project parents
  • Child workspaces inherit projects array

Phase 8 — Agent Context

  • buildBashToolDescription() lists project directories for multi-project workspaces

Phase 9 — UI

  • "Multi-Project" sidebar section
  • Command palette action + creation modal with project picker

Phase 10 — Refactor

  • Centralized MULTI_PROJECT_CONFIG_KEY constant

Generated with mux • Model: anthropic:claude-opus-4-6 • Thinking: xhigh • Cost: $32.73

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 212c278739

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/config.ts
Comment thread src/node/services/workspaceService.ts
Comment thread src/node/services/workspaceService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed all 3 review comments:

  1. Multi-project cwd anchored to container path — AIService now creates MultiProjectRuntime for multi-project workspaces, ensuring getWorkspacePath() returns the _workspaces/<name> container path.
  2. Partial rename rollback — Added rollback logic that reverses already-renamed projects when a later rename fails, with logged errors for rollback failures.
  3. _multi marked as system project — Config entry now sets projectKind: "system" to hide from user-facing project lists.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e03a8da7d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts Outdated
Comment thread src/node/services/workspaceService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed both round-2 comments:

  1. Multi-project init — createMultiProject() now runs postCreateSetup + initWorkspace for each per-project runtime (matching the create() flow), with aggregated completion tracking and cancellation support.
  2. Container rename rollback — If createContainer() fails after renaming all project workspaces, all renames are rolled back before returning the error.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc2bd02e71

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Fixed the remaining comment: rename failure catch block now recreates the old container (with original symlinks) after rolling back per-project renames, ensuring the workspace cwd remains functional even when container recreation fails.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ee3291978

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/multiProject/containerManager.ts
Comment thread src/node/multiProject/containerManager.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Fixed both comments:

  1. Path traversal validation — createContainer() now validates each projectName is a safe path segment (rejects .., path separators, empty names) using path.posix.basename() + path.win32.basename() comparison.
  2. Runtime type guard — Both createMultiProject() and multi-project fork branch now reject non-local/worktree runtimes upfront with a clear error message, preventing SSH/Docker container creation attempts.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a7835cc3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/utils/forkOrchestrator.ts
Comment thread src/node/services/utils/forkOrchestrator.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed both comments:

  1. Fork metadata pass-through — WorkspaceService.fork() now passes parentMetadata to orchestrateFork(), enabling the multi-project fork path for user-initiated forks.
  2. Per-project trust resolution — Multi-project fork now resolves trust from config per-project instead of using a single shared flag. Added regression test verifying per-project trust differentiation.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Rebased on main and resolved conflicts. All previous Codex comments have been addressed and threads resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 18adf5b857

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts Outdated
Comment thread src/node/services/utils/forkOrchestrator.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Fixed both comments:

  1. Per-project trunk in createMultiProject — Each project now resolves its own trunk branch via listLocalBranches + detectDefaultTrunkBranch, with fallback to the caller-provided branch. Supports repos with different default branches (e.g., main vs master).
  2. Per-project fallback trunk in fork — Multi-project fork fallback createWorkspace() now resolves trunk per-project using resolveMultiProjectFallbackTrunkBranch().

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e03c7f3444

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Fixed: fork() now persists projects array on child workspace via forkResult.data.projects ?? sourceMetadata.projects, ensuring forked multi-project workspaces retain all project references on reload.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cbf1cf41cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/utils/forkOrchestrator.ts Outdated
Comment thread src/node/runtime/multiProjectRuntime.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Fixed both comments:

  1. Trust default — Unknown projects in multi-project forks now default to false (untrusted) instead of inheriting primary project trust.
  2. Multi-project init — MultiProjectRuntime.initWorkspace() now iterates over ALL project runtimes, running init hooks for each with per-project paths. Returns aggregate result (first failure if any).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca6f4a93da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts
Comment thread src/node/runtime/multiProjectRuntime.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Fixed both P2 comments:

  1. Project deletion guard — ProjectService.remove() now scans _multi workspace references before allowing project deletion. Projects referenced by active multi-project workspaces are blocked from removal.
  2. Per-project init env — MultiProjectRuntime now supports an envResolver callback for per-project secret resolution during init. The fork flow wires this up to getEffectiveSecrets() per project.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 638d12bfaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts
Comment thread src/node/services/workspaceService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Fixed both P1 comments:

  1. Task fork env resolver — taskService.ts now sets envResolver on MultiProjectRuntime after fork, resolving per-project secrets via getEffectiveSecrets() with a per-project cache. Matches the existing workspaceService.ts fork pattern.
  2. Container deletion ordering — remove() now attempts per-project deletions first and only removes the container after confirming all succeeded (or when force=true). Container stays intact if a deletion fails without force.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d731b5012a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/utils/forkOrchestrator.ts
Comment thread src/node/services/projectService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3928517835

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/aiService.ts
Comment thread src/node/services/taskService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed both new findings:

  1. P1: Secrets merging — Added mergeMultiProjectSecrets() in aiService that collects secrets from all constituent projects, with primary project precedence on key conflicts.
  2. P2: Queued task fallback — When parent metadata lookup fails but the queued task has a projects array, we now synthesize minimal metadata to preserve multi-project fork behavior.

Keep findWorkspace.projectPath aligned with the stored config key for multi-project workspaces so mutation callers still work under _multi, and thread a separate attribution path to stream-end analytics ingestion.

Add focused regressions for both the config semantics and analytics attribution behavior.
Mark queued tasks interrupted when trust is revoked before dequeue, including secondary projects in multi-project task metadata.

Add focused TaskService coverage for primary and secondary trust revocations so queued tasks do not retry forever.
Aggregate git file completions per project for multi-project workspaces and add focused regression coverage.
Concatenate every matching Tool section within a single instruction source so flattened multi-project AGENTS content keeps each repo's tool rules.

Add focused markdown and system-message regressions covering ordered multi-match extraction and context-over-global precedence.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Rebased workspace-yw8w onto the latest main. Please take another look.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1dc605d7fd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts
Normalize repo-root project path matching in WorkspaceService and return '.' for project-root repo-relative selections, with focused regression coverage.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed the remaining repo-root path handling issues:

  • normalized repoRootProjectPath matching in workspaceService
  • normalized project-root selections to . in executeBash

Local validation:

  • bun test src/browser/utils/executeBash.test.ts src/node/services/workspaceService.multiProject.test.ts
  • make typecheck
  • make static-check

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Resolved the lingering repo-root path thread after landing the normalization fix. Re-running Codex/CI readiness checks now.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
ThomasK33 added this pull request to the merge queue Mar 11, 2026
Merged via the queue into main with commit 75bed58 Mar 11, 2026
40 of 42 checks passed
@ThomasK33
ThomasK33 deleted the workspace-yw8w branch March 11, 2026 09:01
yermakoffivan pushed a commit to yermakoffivan/mux that referenced this pull request Sep 25, 2026
…oder#4486)

## Summary

Deletes dead helpers under `src/node` and `src/common` whose only
callers were their own tests (WS2 sweep of production exports with zero
production callers).

## Removed

| Symbol | Evidence |
| --- | --- |
| `snapshotTranscriptAnchor` (+ module, 2 tests) | Only production
caller removed in coder#3816 |
| `createLatestValueQueue` (+ 3 tests) | Last caller replaced by Effect
streams in coder#4039 |
| `withQueueHeartbeat` (+ module, test file) | Same (coder#4039).
`SUBSCRIPTION_HEARTBEAT_INTERVAL_MS` moved to
`src/constants/orpcSubscriptions.ts` (used by `streamBridge.ts`) |
| `createWorktree`, `pruneWorktrees` (`git.ts`, + 2 tests, unused
types/imports) | Worktree creation moved to `WorktreeManager` (coder#457);
`pruneWorktrees` had no references at all. The `listLocalBranches` test
stays |
| `discoverGitRoots` (+ module, test file) | Added in coder#2759, never wired
in |
| `listSessionWorkspaceIdsWithHistory`,
`listArchivedSubagentWorkspaceIds` | Analytics uses
`discoverAllWorkspaces` since coder#2665. The unreadable-transcript-dir and
missing-`chat.jsonl` cases were moved onto `discoverAllWorkspaces`,
which has the same branches and no other test for them |
| `hasUserPreferenceEntry` | No production caller; tests now read keys
via `entriesFromUserPreferences`, as `UserPreferencesContext` does |
| `AgentPluginInstallsSchema` (+ barrel re-export) | No references
anywhere (`plugins.json` is parsed as a record in the registry) |

## Validation

- Mutation check: making `discoverAllWorkspaces` rethrow unreadable
transcript-dir errors fails the moved test.
- Every name `git grep`-checked across the repo (src, tests, scripts,
vscode, docs).
- `make static-check` on the stack top; affected suites pass.

Stack: 4/5.
---

_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking:
`high` • Cost: `$4.45`_

<!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high
costs=4.45 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant