Organizing Execution
Worktrees and Isolated Environments
What a Git worktree actually isolates, which state stays shared, how a worktree differs from a branch, a clone, a container and a sandbox, and how to…
Implementing a vertical slice followed a change from intent to merge, and agent task contracts showed how to agree the outcome, the boundaries and the evidence with an agent. This unit looks at the physical and technical environment the agent works in. It is a continuation of the task contract: name not only the repository and the branch, but also which files, processes, data and accounts the worker genuinely has to itself.
Learning outcome. After this unit the reader can explain what exactly a Git worktree isolates, which state remains shared, how a worktree differs from a branch, an ordinary clone, a container and a sandbox, and how to check the boundaries before parallel work begins. Being able to name a specific resource matters more than the general claim that "the agents work in isolation".
Prerequisites and scope. The unit builds on the ideas of a branch, a commit, uncommitted changes and verifying a result, covered elsewhere in the course. It is not a list of commands to memorise, and it does not describe starting or deleting real environments. Managing a product's environments more broadly is the subject of Environments; here the focus is the difference between a copy of the files, Git state, a running process and external resources.
Isolation always refers to a specific boundary
The word "isolated" is incomplete until it answers the question: isolated from what, exactly? A worktree can separate the files of one branch from the files of another, but it does not automatically create a separate database, operating-system user, network namespace or cloud account. Conversely, a container can run a program separately, while a directory mounted into it may still be shared with the host.
| Layer | What is usually separated | What may remain shared |
|---|---|---|
| Git branch | A pointer to a different tip of the commit history | The files in the same working tree, and uncommitted changes |
| Git worktree | The checked-out files, the index and the current HEAD of each working copy |
Most of the object store, the references, the repository configuration, and every external resource |
| Ordinary clone | The repository directory and its local Git metadata | The remote repository, shared services, users and local caches |
| Container | The configured file system of the process and part of its runtime environment | Mounted directories, published ports, passed-in credentials and reachable services |
| Sandbox | Whatever file, network or command permissions the policy sets | Everything the policy allows; the name alone does not define the set of permissions |
| External service | Only the separate accounts, namespaces or instances that were actually created | The database, queue, file storage or application programming interface (API), if both workers can reach it |
This is not a reliability ranking of tools. Each mechanism solves a different problem. For a task contract it is more useful to write "separate copy of the files, shared test database and shared server port" than to call the environment simply isolated.
What a Git worktree separates
A branch is a movable reference to the tip of a sequence of commits. A working tree is the repository's files on disk: a particular checkout plus any uncommitted changes. A worktree is a working tree together with the Git metadata that this particular copy needs.
Git normally has one main working tree and may have several linked working trees. Each linked worktree has its own checked-out files, its own index and its own HEAD — the name Git uses for the current revision in that working tree. So an uncommitted edit to settings.py in one directory does not overwrite the file of the same name in another. A commit is also made in a specific working tree, although the new Git object immediately belongs to the shared local store of that repository.
A worktree is not, however, a miniature clone of the repository. Working trees share most of the Git data, including the store of commits and files. Most branch references are shared; each tree's own HEAD says which revision it is currently showing. The repository configuration is shared by default too. Git supports a special setting for per-worktree configuration, but it is an option with compatibility conditions, not automatic behaviour (the git worktree reference).
There is a practical restriction: the same local branch normally cannot be checked out in two worktrees at once. This prevents the confusion of two working trees competing to move one branch pointer. Separate worktrees should work on separate branches, or on separate revisions with no branch attached (a detached HEAD); the choice of base revision still has to be recorded.
A clone draws a different boundary. An ordinary git clone creates a separate repository directory with its own local Git metadata and its own remote configuration. To see commits from another clone you usually have to fetch them from the remote, or push them there. A worktree saves disk space and avoids fetching the history again when you need several checkouts of one local repository; a clone separates Git configuration and local history better. This is a trade-off, not a universal advantage: an ordinary clone does not create a separate server, database or account either. Special cloning options can reuse an existing object store, so the word "clone" on its own does not guarantee full physical independence.
It is important to distinguish the visibility of a commit from the visibility of a file. Two worktrees can see the same Git object and different branches. If an agent makes a commit in one working tree, the other directory is not rewritten automatically; it stays on its own checkout. To integrate the change, its branch has to be brought together with the target branch by whatever method the team has chosen, and the resulting state has to be checked.
The base revision and local state
Two agents that started from the same branch may be reasoning from different foundations. Before creating a worktree, record the base revision: for example, the identifier of the commit the work started from. The name main may move later. A note like "started from main" does not say which code the agent actually saw.
A separate folder does not automatically recreate the whole environment. The dependency manifest and its lockfile may be committed, but installed packages, compiled artefacts and caches are often ignored by Git. They may be missing from a new worktree. If they were copied or installed, the package manager's cache may be shared at the user level, as may a globally installed runtime version. A symbolic link or a shared cache directory can tie together content that at first glance looked local.
A generated directory may be separate in each copy while the build configuration points both copies at the same absolute path outside the repository. Conversely, a local dependency may differ between folders even though both agents started from the same lockfile. The condition "both agents are on the same branch" does not prove that the full environments are identical.
Ignored local files are a separate trap. Git does not include them in commits, so a standard checkout of a new working tree does not guarantee that .env, a local SQLite database or a private certificate will be there. Codex Desktop and Claude Desktop describe copying the files listed in .worktreeinclude for their managed scenarios; in Codex this applies only to local desktop worktrees, not to remote ones or ones created manually through Git (Codex worktrees, Claude Desktop). This is client behaviour, not Git behaviour. Hand secrets over deliberately: copying a file does not create a new key, user or access boundary. Separate text configurations may still point at the same external account.
For two parallel agents, define not just "the same dependencies" but the level of reproducibility you need: which file pins the versions, how installed dependencies are prepared, what has to be generated, whether a cache is shared, and which configuration variables point at local or shared resources. If preparing a new worktree runs a product's setup procedure, that behaviour belongs to the settings of that particular client, not to Git.
Processes, ports and external data
A worktree creates a working copy, but it does not run each process in its own operating system. Two agents can start servers on the same computer and ask them both to listen on the same port. The first process may take it, and the second may either exit with an error or quietly attach the check to the server that is already running. A check that returned a page with the expected text does not prove the response came from the new code.
The same applies to background servers, builds, file locks, temporary files and caches created outside the checkout. The local address localhost refers to the network namespace of the current machine or container, depending on the setup; several processes in the same network namespace can still compete for a port. Separate container network namespaces change the picture, but a port published to the outside still needs a unique assignment. Establish which process is running, where it was started from and which directory it reads; do not stop an unknown process merely because it is listening on the port you expected.
The most obvious shared state often lies outside Git altogether. Two sets of files may talk to the same database, message queue, object storage, external API or test account. Then applying a migration, clearing tables or processing a queue from one copy affects the other.
Separating external data may require a separate test database or an isolated schema, a unique namespace for queues and files, dedicated test credentials, small controlled fixtures (prepared data for the checks) and explicit clean-up. The boundary that matters is the actual service the configuration points at. A separate schema does not help if a migration changes a shared schema; a separate database name does not help if an environment variable still redirects to a shared server under a different identifier; a test queue does not isolate shared file storage.
The same goes for access rights. Two .env files may contain the same key. Two agents may act as the same cloud user or send requests to a shared test environment. Development should use the least privilege needed and test credentials with no access to production data. A physical copy of a directory is not permission to apply a migration or write to a live system.
Worktree, container and sandbox are different axes
A container is a way of running a process in a configured runtime environment. Its boundary depends on the image, volumes, environment variables, network, process permissions and the tool that manages it. Mounting a host directory can let the container edit files on the host; mounting it read-only changes exactly that property, but does not separate data the process can reach over the network. Docker documents explicitly that a bind mount is writable by default unless it is mounted read-only (Bind mounts).
A sandbox is a set of restrictions on actions: access to file paths, the network, child processes and other resources. It can restrict the behaviour of a command run from any directory, but it is not obliged to create a separate Git repository or separate data. The presence of a confirmation dialog is not evidence of file or network isolation either: it is a rule for granting permission, not a new environment.
So it helps to describe at least three decisions independently: which checkout the agent sees; where, and with which tools, its process runs; and which external systems and permissions it can reach. For example, Codex can work in a Git worktree in local mode, in a remote Cloud container, or in another configuration; these modes differ by more than a single switch. Claude Code has its own worktree support and its own sandbox restrictions, which also depend on the platform. A feature of one client is no guarantee of another client's behaviour.
Product documentation defines its own lifecycle. As of the date it was checked, Codex Desktop creates a managed working tree from the HEAD of the selected branch; if there are local uncommitted changes, the documentation says they are carried over into that tree, which starts in a detached HEAD. These trees are tied to individual chats; a permanent worktree exists as a long-lived environment and is not deleted automatically. In Codex, .worktreeinclude applies to local managed desktop worktrees, not to remote ones or ones created manually through Git (Codex worktrees). Claude Desktop creates session trees in <project-root>/.claude/worktrees/ by default and lets you change the location; a finished local session can be archived. Claude Code on the command line also supports a --worktree option and by default creates the tree in <repo>/.claude/worktrees/<name> (Claude Desktop, Claude Code worktrees, CLI reference). These details depend on product versions and surfaces; check them separately rather than assuming they hold for every Git worktree.
Git describes worktrees in a platform-neutral way, but support in tools, shells and sandbox rules varies. Claude Desktop requires Git for Windows for worktree sessions; the Bash sandbox in Claude Code works on macOS, Linux and Windows Subsystem for Linux 2 (WSL 2), but not on native Windows. Check commands, paths and access against the documentation for your current product and environment rather than carrying them over from another operating system.
Three worked scenarios
Scenario 1: separate worktrees, one test database
Imagine a test booking service. One agent, Marta, is given her own working tree for a migration of the bookings table. Another agent, Taras, works in a second tree on the cancellation handler. Their branches and uncommitted files are independent. Both .env files point at booking_test, the same test database.
Marta runs a migration that renames the table, and before her check she clears out the old data. Meanwhile, Taras's test reads the same table. It fails because the table name no longer exists, and then Marta seeds a set of bookings and Taras gets someone else's results. This is not a file-system conflict: each worktree stayed intact. The conflict arose in the shared external store.
Adequate protection starts by checking the actual endpoint of the connection, not just comparing .env files. For independent parallel verification, each branch gets its own test database or a managed data space in which migrations, fixtures and clean-up are confined to that piece of work. If that isolation is not possible, the team should serialise work on the schema and data, forbid destructive clean-up, and write down explicitly that the checks share state. Test credentials must not make it possible to connect to the production database.
This matches a specific user report in Cline Kanban discussion #254: one operator, describing several agents working on a Laravel and Sail project with a PostgreSQL database (a relational database management system that uses SQL, the Structured Query Language), reported a shared database, migration conflicts and an impact on the main local database. It is a self-report from one participant in a discussion, not a measurement of how often the problem occurs and not evidence about every system. It helps to articulate the risk, but it does not replace checking your own configuration.
Scenario 2: the port is taken, yet the page still responds
In two copies of an application, a preview server is started on the default port 3000. The first copy starts its process correctly. The second start fails to take the port, but the test browser stays open on the first copy's address. The response looks plausible, so the agent may wrongly report that it has checked its own change.
Before drawing a conclusion, establish which process is listening on the port, which directory it runs in and which branch it was started from. The second copy's server should get a separate port, or a separate network namespace with the port published correctly. The report should name the exact server and checkout the check was run against. If the source of the response is unknown, the result is not "verified" but "could not confirm that the intended revision was running".
A real technical example of a shared port space is described in the merged Paperclip pull request (PR) #9642 (description and checks). Two working copies could use a shared worktree directory but identical ports for the server and for embedded PostgreSQL. The initial scan of neighbouring configurations did not detect a copy under a different root directory, and parallel configuration recovery allowed duplicate assignments. The PR added a shared reservation registry with locking. It is an example of a specific race in allocating a resource, not proof that any worktrees automatically conflict; the PR records 14 focused tests, a passing type check and 24 passing GitHub checks.
Another limited report shows how the wrong copy can end up being checked. The author of Claude Code issue #63008 described a worktree session on macOS in which the Claude Preview mechanism started a child server with the main repository as its working directory. According to the report, the page served the main branch, wrote its cache into the main copy's .next directory and occupied port 3000; the author confirmed the process's directory with the lsof utility ("list open files"). The issue was closed as "not planned", which means neither that it was fixed nor that this is general product behaviour. It is a self-described case on a particular version of macOS and Claude Code (issue #63008), so before drawing conclusions, check the working directory of the process in your own environment.
Scenario 3: two changes integrated one after the other onto the current base
Two agents start from commit M0. The first changes the booking schema; the second changes the cancellation form and its expectations of the API. Initially their files are different, so each branch passes its own narrow checks. After the first agent's branch is merged, a new revision M1 appears. The second agent's change still descends from M0 and may be incompatible with the new schema, even if there are no textual conflicts.
Before integrating, the author checks whether the target commit has changed since the second piece of work began, and which contracts depend on the first change. Then the second branch is updated and reconciled with M1 in the agreed way, without losing its uncommitted changes and without rewriting someone else's branch. After that, the compatibility of the schema, requests, migrations and user journey is checked again on the integrated revision. Checks run by each branch separately on M0 remain evidence about M0 only; they do not vouch for the combined result.
Once both branches are integrated, the combined revision is checked with its actual dependencies and the required environment. A clean merge confirms that the system managed to combine the history; it does not prove that the changes are logically compatible. Before deleting any working copy, find out whether it still holds uncommitted files, unrecorded evidence or a needed unmerged commit. Integration and clean-up must preserve that state until responsibility for it has been handed over.
How to agree on a parallel environment
Before work begins, the author, the agent or the orchestrator should answer the questions below in the task contract or in the linked environment configuration. Not every task needs every resource, but an answer of "not applicable" should be a deliberate one.
| Question | A clear agreement |
|---|---|
| What state does the work start from? | An exact commit or other labelled revision; the branch name; the rule for updating after the target branch changes |
| Which files are separate? | The worktree path, the expected state of ignored/local files, setup dependencies, build directories and caches |
| Where do processes run? | Separate ports or process/network namespace; the owner of each background server; where the check is run from |
| Which data changes? | Test database, schema, queue and storage; namespace; fixtures, forbidden actions and the clean-up boundary |
| What access is there? | A test account and the least permissions needed; no production secrets unless they are needed and approved |
| Where is the container or sandbox boundary? | Mounts, accessible paths, network rules, passed-in variables and exceptions |
| How does integration happen? | The integration owner, when to sync with the base, mandatory re-checks on the integrated revision |
| What does clean-up allow? | Who may stop processes, delete temporary resources, and when data is no longer needed; unknown processes and uncommitted changes are preserved |
Do not turn this list into needless formality for a short task. It is needed when a boundary mistake could destroy data, substitute a different revision for the one that was checked, or give the agent access the author did not intend.
Finishing work and cleaning up
A task does not end automatically once the branch is merged. Make sure the changes and the evidence needed have been recorded or handed over; check that the workflow has not left a server running; clean up only the temporary data, processes and environments you actually own. Deleting a directory must never be a way of getting rid of someone else's work or uncommitted files.
Git protects against some accidental actions: the standard removal of a linked worktree refuses to remove a working tree that is not clean. This protection is useful, but it does not replace checking the owner and the state, and forced removal bypasses it. In the same way, clearing a local test database should be limited to your own temporary space, not done on the strength of a similar name or an assumption that nobody is using it.
The practical completion criterion is simple: it is known where the commits, uncommitted changes, processes and test data are; their owner has agreed the next step; the check was run on the right integrated revision; and clean-up did not touch any state outside this agreement.
Summary
A Git worktree gives you several physically separate checkouts of one repository. It keeps the index and HEAD local to each copy, while most of the history and Git metadata stay shared. On its own it does not isolate caches, background processes, network ports, databases, queues, accounts, containers or network access.
Name the resource, its owner, its boundary and how it will be checked. A separate directory does not prove that a test touched the right server or data. A separate container does not guarantee that mounted directories and external services are isolated. After changes are combined, re-check compatibility on the actual integrated revision; before cleaning up, preserve any unfinished state, and stop only your own processes.
Sources and further reading
- Git, git-worktree and the Git glossary — official terms, shared and per-worktree metadata, and the restrictions on branches, state and clean-up.
- Git, git-clone — the boundary of an ordinary clone, and notes on options that use a shared object store.
- OpenAI, Git worktrees, Codex environments and sandboxing — capabilities and settings of specific Codex modes; not Git rules and not assumptions about other clients.
- Anthropic, Claude Desktop, Claude Code worktrees, the command-line interface (CLI) reference and Claude Code sandboxing — product support for worktrees and the platform limits of the sandbox.
- Docker, Bind mounts — how a mounted directory can leave a container with write access to the host file system.
- Paperclip, PR #9642 — an implemented mechanism for reserving ports that would otherwise collide between worktrees; a concrete example of a race.
- Cline Kanban, discussion #254 — an operator's self-report of a shared PostgreSQL database across parallel agent working copies; limited evidence, not general statistics.
- Anthropic Claude Code, issue #63008 — a single self-report of a preview server started from the main copy during a worktree session; not to be presented as universal product behaviour.
The changeable, product-dependent capabilities and repository states were checked on 26 September 2026. The Git page stated that its last substantive update was version 2.54.0; check the local documentation for compatibility with your installed version.