Conversation
A pooled workspace is a fixed set of interchangeable environment directories, each holding the same child git repositories side by side. A task leases one whole environment rather than building a worktree, so its installed dependencies and warm build caches are used in place. This adds the state that shape needs: a fourth git isolation mode, the project's pool configuration, and the leased environment and per-repo branches on a task. Persisted rows are validated on load like every other hand-editable field, since every pool git call is driven from them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Which repositories a pooled environment holds comes from a manifest the workspace already keeps for its own setup script, or from a scan one level down when there is none. A manifest is preferred because it names repositories that belong to the workspace even while they are missing, which is what lets "you have not run the setup script yet" stay a different answer from "this environment has no repositories". Member names become path segments under the environment root, so they are validated rather than trusted: a manifest is a file inside a checkout, and a checkout is not a trust boundary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Leasing checks an environment is ready before it takes it, and reports every blocker at once rather than one per attempt: three round trips to find three dirty repositories is the failure mode worth avoiding. Every member repository gets the task's branch, not just the ones the change turns out to touch — one branch name across every repository a change spans is the convention these workspaces already follow, and an unused branch is deleted again on release. Creation is all-or-nothing, because a half-branched environment would be leased again, found clean, and quietly inherit the leftovers. The lease is written into the environment as well as held in app state, so a second app instance, or a person in a terminal, can see the environment is taken. A lease whose task no longer exists is reclaimed rather than stranding the environment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A pool task's working directory is the leased environment itself, so every path-keyed feature — shells, canvas, browser preview, verify — points at it exactly as a worktree task points at its worktree, with no change needed. Branch names are derived in the main process beside the worktree ones, so a pool task and a worktree task of the same name get the same branch. Closing a pool task releases the environment instead of removing a worktree: member repos go back to their base branch, unused task branches are dropped, and the lease is cleared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A pool task's change lives in several repositories at once, so the changed files and the diff are the union of its member repositories rather than one repository's answer. Aggregation happens in the main process, and each path is re-rooted under its repository name — which makes it a real path relative to the environment root, the very path these panels already hold. Opening a file in an editor and resolving it back to the repository that owns it both keep working with no change to the panels themselves. Commit navigation stays off for pool tasks: it is per-repository, and a pool task spans several. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two tasks in a pool run two copies of the same applications, so they cannot both serve on an application's default port — today the Winston environments all resolve the till to 3501, which is why only one of them can serve at a time. The port belongs to the environment rather than to the task: a lease comes and goes, but someone who learns that MRW2 serves the till on 3511 should keep being right. A task's terminals get PORT for the common single-application case and PARALLEL_CODE_PORT_<REPO> for the whole map, and a project that configured no ports is handed nothing rather than a misleading PORT. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pooled environments are typed once and changed rarely, so the project dialog edits them as plain lists rather than as repeating row widgets — a textarea someone can paste five paths into beats five pickers. Clearing the paths is what turns a pooled workspace back into an ordinary project, so there is no separate "is a pool" switch to leave inconsistent. The new-task panel offers the mode only where a pool exists, and says how many environments are free before the create button rather than as the error the attempt would otherwise be. The close dialog says what handing an environment back actually does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A pool task's branches live in several member repositories, so merging and pushing fan out over them. Both skip a repository with no commits, so the branches that reach the remote are the ones the change actually touched, and both run sequentially: a conflict in one repository means the change is not integrated, and carrying on would leave half of it on base. Push output is labelled per repository by the backend rather than interleaved, because several `git push --progress` streams at once are unreadable and the platform wants a pull request per repository afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Why a worktree is the wrong shape for a directory of git repositories, how a lease is taken and given back, how a change is shown across repositories, how ports are laid out per environment, and what the mode does not do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A pool task's environment root holds no code of its own — its member repositories do — so asking git about that one directory reported every pool task as unchanged forever. The task badge now rolls the members up: any repository with changes makes the task changed, and the branch is reported only where every repository agrees on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things stopped the mode fitting a real multi-repository workspace. A repository the manifest declares but nobody cloned was a blocker, so an environment was refused over a stale line in a file. The Winston dev-env's repos.tsv names three repositories that are not cloned and omits three that are — an environment like that would never have been leased. It is now reported and the environment is still usable. The workspace's own repository was not a member either, so a change touching the manifest, a shared script or the instructions at the root went untracked. It participates as `.`, and because its files already sit at the environment root, it is the one member whose paths are not re-rooted — which also makes it the fallback when routing a path back to the repository that owns it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A shared library checked out both as a sibling repository and as a submodule inside each application that uses it has to be edited in the submodule copy for the running application to pick the change up, but committed in the sibling checkout, which is the one with a branch. The copies are pinned independently — the Winston applications pin four different commits of `shared`, none of them the sibling checkout's tip — so carrying a diff between them is a patch across a pin gap that can conflict, or apply cleanly against stale code. Aligning first removes the gap: every copy is put on its canonical repository's base branch when the environment is leased, fetched from the checkout on disk rather than over the network, so a later patch applies by construction and what is committed is what was actually run. "Sync shared" then carries each copy's changes across — commits made inside it as well as uncommitted work — and leaves the result uncommitted, because the message belongs to the author. Pushing refuses while a copy still holds changes the canonical checkout has not taken, so an application branch is never sent without the shared change it was written against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
A fourth git isolation mode,
pool. Instead of building a worktree per task, a task leases one of a fixed set of ready environments, branches every repository inside it, and hands it back on close.It exists for projects that are not one git repository but a directory holding several side by side, where getting one ready is expensive enough that copying it per task is not worth it — installed dependencies, initialised submodules, warm build caches.
git worktreecannot express that shape: a worktree of the container is an empty container,git worktree adddoes not populate submodules, and a change spanning three repositories wants the same branch name in each while aTaskholds one.Written generically: a pooled workspace is any directory of interchangeable environments, each containing child git repositories, discovered from a manifest (
name<TAB>url[<TAB>branch]) or by scanning one level down.How it stays small
task.worktreePathbecomes the leased environment root, so shells, the canvas, the browser preview and the verify command are unchanged — a pool task isdirectmode generalised. Only the git surface had to learn that a task spans several repositories, and that is additive: every function inelectron/ipc/git.tsis keyed on a path, so the fan-out is a loop plus aggregation.Changed files and diffs are the union of the member repositories with each path re-rooted under its repository name, which makes it a real path relative to the environment root — the path those panels already hold — so opening a file in an editor and routing a per-file diff back to its owner both fall out of it.
Shared libraries checked out twice
Some workspaces keep a shared library both as a sibling repository and as a submodule inside each application that uses it. The application builds against its copy, so that is where a change must be made, but the copies are pinned independently and drift. On lease every copy is put on the canonical repository's base branch (fetched from the checkout on disk, no network), so a later patch applies by construction; Sync shared then carries the copy's changes into the canonical checkout, uncommitted; and push refuses while a copy still holds changes the canonical checkout has not taken.
Documentation
docs/pooled-workspaces.mdcovers configuration, the task lifecycle, per-environment ports, the shared-library flow and the limits.Testing
npm run checkis green. 54 new tests across five modules; the lease, readiness, release, fan-out and shared-library suites run against real git repositories (including real submodules with a deliberate pin gap) in temporary directories.Note for the maintainer: four tests fail on
mainat4bb51fabefore this branch — one insrc/lib/reduced-motion-styles.test.tsand three insrc/components/DiffViewerDialog.client.test.tsx. I left them alone; they are unrelated to this change.Known limits
🤖 Generated with Claude Code