As soon as there is more than one task, the question of parallel work without conflicts arises, and the base mechanism here is the git worktree. A worktree gives a separate checkout, its own branch and working directory with a shared git object database. It is the best foundation for parallel changes precisely because the agents do not edit the same set of files: each has its own tree, and one's edits physically do not overlap with another's.
A worktree is created with the --worktree or -w flag, including directly by pull request number. By default it appears under .claude/worktrees/<name> on a branch worktree-<name>, and the base is set by worktree.baseRef: fresh takes the remote default branch (a clean tree under the remote), head the current local HEAD with your unpushed commits. A worktree is a fresh checkout of tracked files only, so the dev environment in it (dependencies, setup) is initialized anew.
A separate subtlety - files ignored by git do not appear in the worktree on their own. For them there is .worktreeinclude: it copies only the files that both matched the pattern and are gitignored - for example, a local .env. The safety rule is simple: copy secrets only when truly needed and when the path is protected by the sandbox and permissions. Thoughtlessly moving .env into all worktrees means multiplying the secret across copies of the repository.
It helps to see the whole worktree setup once. Below is baseRef (what to branch from), sparsePaths and symlinkDirectories for a large repository and a reminder about .worktreeinclude. You return to this form when setting up parallel work: fresh gives a clean tree under the remote, head carries your unpushed commits, sparsePaths leaves only the needed directories on disk via git sparse-checkout, symlinkDirectories avoids copying heavy shared folders like node_modules, and gitignored files like .env are carried by .worktreeinclude.
Background sessions move work out from under your attention. They are launched from the interactive mode with the /background command or from the shell with the --background flag, and the list is opened via claude agents (including --json for machine parsing). They are managed by a set of commands: attach connects, logs shows the output, stop stops, respawn restarts, rm removes the job and the safely created worktree when possible. The transcript meanwhile stays available for resuming.
The agent view is a single console of background sessions. claude agents shows their states: working, blocked, done, failed, stopped. Especially useful is the waitingFor field in JSON: it explains what exactly a session is blocked on - a permission prompt, input needed, a sandbox request, a worker request or a dialog. From this comes practical monitoring: one jq query can pull all blocked sessions and see what each is waiting for, without opening them one by one.
Preventing conflicts comes down to a few rules. One task that changes files gets its own worktree; a research-only subagent can work without one. Two agents are not assigned the same files. The merge is done by a human or a separate integration task after tests. Before deleting a worktree you check the commits and untracked files. And you symlink only heavy shared directories like node_modules via symlinkDirectories, but not writable source - a shared writable source between worktrees breaks the very isolation the whole thing was for.
The engineering conclusion about parallelism is simple: it is justified only where the work is truly independent. A swarm of agents is a spend multiplier, and launching it for a task that a single pass solves linearly is more expensive and dumber. The typical failures are two agents on the same files, symlinking writable directories and deleting a worktree without checking the uncommitted. Give each file-changing task its own tree, and parallelize only what truly parallelizes.
{
"worktree": {
"baseRef": "head",
"sparsePaths": ["services/api"],
"symlinkDirectories": ["node_modules"]
}
}
// baseRef: "fresh" (remote default branch) | "head" (local HEAD)
// sparsePaths: check out only these dirs + root files (git sparse-checkout) - faster in monorepos
// symlinkDirectories: symlink large shared dirs (node_modules) instead of copying to disk
// .worktreeinclude copies gitignored files (.env); secrets - only with a path protected by sandbox/permissions# Background sessions and the agent view
claude --background "investigate the flaky test and prepare a report"
claude agents --json | jq '.[] | select(.state=="blocked") | {id,name,waitingFor}'
# management: attach | logs | stop | respawn | rm <id>