preview_start resolves .claude/launch.json from the session cwd, not the calling agent's, so concurrent worktree lanes must contend for one config file

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 29, 2026

Summary

preview_start resolves .claude/launch.json from the session's working
directory rather than from the working directory of the agent that called it. A
subagent running in its own git worktree therefore cannot have its own preview
configuration: its name argument is matched against the orchestrator's
launch.json, and the server it gets (or the error it gets) belongs to another
directory tree.

The consequence is worse than a wrong path, because the only way for such a lane
to make preview_start work is to write its own entry into a file that every
other concurrently running lane is also reading and writing. Four lanes each
needing their own port and their own -C <worktree> argument, all editing one
.claude/launch.json, is a silent last-writer-wins race: nothing errors, and a
lane ends up serving a sibling lane's worktree under its own port.

Environment

  • Claude Code with concurrent Agent subagents using isolation: "worktree"

(each lane's cwd is its own git worktree, either .claude/worktrees/<name>/
or an external checkout).

  • Observed 2026-08-24 on macOS during a five-lane wave in a private repository.
  • Also reachable with EnterWorktree rather than agent isolation, since the

distinguishing factor is only that the caller's cwd differs from the session's.

Minimal reproduction

  1. In a git project, create .claude/launch.json with one configuration that is

specific to the project root:

``json
{
"version": "0.0.1",
"configurations": [
{
"name": "root-web",
"runtimeExecutable": "python3",
"runtimeArgs": ["-m", "http.server", "3000"],
"port": 3000
}
]
}
``

  1. Create a worktree and give it a different configuration under its own

.claude/launch.json, with a distinct name and port:

``bash
git worktree add ../proj-lane -b lane
mkdir -p ../proj-lane/.claude
cat > ../proj-lane/.claude/launch.json <<'JSON'
{
"version": "0.0.1",
"configurations": [
{
"name": "lane-web",
"runtimeExecutable": "python3",
"runtimeArgs": ["-m", "http.server", "3101"],
"port": 3101
}
]
}
JSON
``

  1. From a session opened at the project root, launch a subagent whose cwd is

../proj-lane (isolation: "worktree", or any agent invoked with that cwd),
and have it call:

``
preview_start({ name: "lane-web" })
``

Observed. The call does not find lane-web. It resolves
.claude/launch.json from the session's cwd — the project root — so the only
configuration in scope is root-web, and the attempt proceeds against that
config's port 3000 (in our case failing on it, since the orchestrator already
held 3000).

Expected. preview_start resolves .claude/launch.json from the calling
agent's own working directory, so lane-web is found and started with cwd
../proj-lane on port 3101. Falling back to the session's file when the caller's
tree has none would be a reasonable addition; reading the session's file *instead
of* the caller's is the defect.

Why the fallback is not a workaround

The natural workaround — adding a lane-web entry with
"cwd": "<absolute worktree path>" to the session's .claude/launch.json — is
what makes this a data-integrity problem rather than an ergonomics one. With N
concurrent lanes:

  • every lane needs a distinct port and a distinct absolute cwd;
  • all N write the same file, and no write fails;
  • the last writer wins, and an earlier lane's preview_start then spawns a

server rooted in another lane's worktree, on a port that lane believes it owns.

Nothing in that sequence produces an error, so the first signal is a developer
looking at a preview pane that is serving the wrong branch's code.

Suggested fix

Resolve .claude/launch.json relative to the calling agent's cwd, walking up to
the enclosing git worktree root, and only then fall back to the session's
project root. That makes a lane's preview configuration lane-private by
construction and removes the shared file entirely.

Related

#86039 reports the
mirror-image defect one level down: relative cwd values inside launch.json
resolving against the session worktree on UI-initiated launches. That issue
notes preview_start resolves those values correctly against the project root —
this issue is about the discovery of the launch.json file itself, which
preview_start resolves from the session cwd rather than the caller's, and is
independent of how paths inside the file are then interpreted.

Tracked downstream as
narduk-enterprises/agent-infrastructure#845,
where the local mitigation is a PreToolUse hook that refuses a write to a
.claude/launch.json outside the calling agent's own working tree — it blocks
the dangerous workaround, but it cannot make preview_start usable from a lane.

View original on GitHub ↗