Skip to content

Review flow breaks in worktrees: MCP cwd anchoring + /review skill @file dispatch handshake #397

@ncrmro

Description

@ncrmro

Two distinct bugs surfaced while running the /review flow on a PR branch from inside a git worktree. They are mechanically independent but both block worktree-based review workflows.

Tested with deepwork==0.13.3 via the MCP server.


Bug 1 — mcp__deepwork__get_review_instructions anchors at the parent repo, not the dispatcher's cwd

When a Claude Code session is launched from inside a git worktree, the deepwork MCP server still resolves file paths and git diff from the parent repo's working tree, not the worktree the dispatcher started in.

Observable symptoms

  1. Path strings returned in get_review_instructions task fields are relative to the parent repo cwd. They only resolve correctly from the parent's working directory.
  2. Instruction files are written under the parent repo's .deepwork/tmp/, not the worktree's.
  3. Change-detection (git diff against the main branch) runs on the parent's working tree. If the parent is on main and the worktree has the PR branch checked out, the harness reports the diff for main (essentially nothing or whatever's untracked in the parent), not the PR diff.
  4. unchanged_matching_files: true produces empty packs for files that exist only on the worktree branch.

Why this happens to "work" sometimes

If the worktree happens to be nested inside <parent>/.claude/worktrees/<name>/, then the MCP-emitted relative path <.claude/worktrees/<name>/.deepwork/tmp/...> resolves to the same absolute filesystem location as <worktree-cwd>/.deepwork/tmp/.... So instruction files appear in the right spot by accident of nesting.

A worktree placed elsewhere — for example via git worktree add ~/some-other-path/... — breaks entirely: every emitted path points to a non-existent location relative to the dispatcher.

Repro

# Set up a minimal repo with one .deepreview rule and a Python source file
mkdir /tmp/dw-repro && cd /tmp/dw-repro
git init -q && git commit -q --allow-empty -m "init"

cat > .deepreview <<'YAML'
example_review:
  description: "Trivial review."
  match:
    include:
      - "src/**/*.py"
  review:
    strategy: individual
    instructions: "Confirm the file is valid Python."
YAML

mkdir -p src && echo "x = 1" > src/foo.py
git add . && git commit -q -m "add rule and source"

# Branch and modify on a separate worktree placed OUTSIDE the parent
git checkout -b feature/edit
echo "x = 2" > src/foo.py
git commit -q -am "edit src/foo.py on feature branch"
git checkout main

git worktree add /tmp/dw-repro-wt feature/edit
cd /tmp/dw-repro-wt

# Launch a Claude Code session from this worktree and invoke /review
# (or call mcp__deepwork__get_review_instructions directly)

Expected

  • Change detection compares feature/edit (the worktree's branch) against main, surfaces src/foo.py as changed.
  • Instruction files written to /tmp/dw-repro-wt/.deepwork/tmp/.
  • File-content inlining reads src/foo.py from /tmp/dw-repro-wt/src/foo.py.

Actual

  • Change detection runs against /tmp/dw-repro (parent), surfaces nothing (or untracked parent files).
  • Instruction files written to /tmp/dw-repro/.deepwork/tmp/, not the worktree's tmp.
  • Inlined source is read from the parent's src/foo.py (x = 1), not the worktree branch's content (x = 2). Or — if the worktree is nested inside the parent's .claude/worktrees/, the paths happen to collide and inlining works by accident.

Suggested fix

The MCP server should respect the dispatcher's cwd. Options:

  • Honor PWD / GIT_WORK_TREE env vars from the spawning process.
  • Add a repo_root parameter to get_review_instructions and related tools.
  • Detect the dispatcher's cwd via the Claude Code session id and reparent.
  • Emit absolute paths instead of paths relative to an implicit cwd (mitigates the path-string issue at minimum, but doesn't fix change-detection).

Bug 2 — /review skill instructions claim @file macros expand in sub-agent prompts; they do not

The /review skill's "How to Run" section says:

The output will list review tasks to invoke in parallel. Each task has name, description, subagent_type, and prompt fields — these map directly to the Task tool parameters. Launch all of them as parallel Task agents.

And carries an explicit instruction in the get_review_instructions response payload:

IMPORTANT: Do NOT read the prompt files yourself. Pass the prompt field directly to each agent — the @file references are expanded automatically.

This is incorrect. @file reference expansion is a feature of the parent Claude session processing user-typed prompts. It does not apply to prompts handed to sub-agents via the Task / Agent tool. A sub-agent dispatched with prompt @<path> receives the literal string @<path> as its prompt, with no file content inlined.

Repro

  1. From a session with a .deepreview rule that matches at least one changed file, call mcp__deepwork__get_review_instructions.
  2. Take any returned task. Its prompt field will be @.deepwork/tmp/review_instructions/<rule>--<files>--<hash>.md.
  3. Dispatch a sub-agent via the Task tool with that exact prompt.
  4. In the sub-agent's first message or via a probe, ask it to print its initial prompt verbatim.

Expected (per skill docs)

The sub-agent receives the inlined contents of the instruction file as its initial prompt.

Actual

The sub-agent receives the literal string @.deepwork/tmp/review_instructions/<rule>--<files>--<hash>.md as its prompt. To do useful work, it has to Read the file itself — which contradicts the skill's "Do NOT read the prompt files yourself" guidance.

Why this matters

Capable sub-agents (Sonnet/Opus) often work around this by calling Read on the path embedded in their prompt, which masks the bug. But the documented dispatch protocol is broken regardless. Reviews that rely on the harness inlining the instruction (for example, anything that should not give the sub-agent autonomy to decide what to read) silently degrade.

Suggested fix

Pick one:

  • Skill-side: update the /review skill so the dispatcher Reads each instruction file and inlines the contents into each sub-agent's prompt. Drop the "Do NOT read the prompt files yourself" line. Loses a minor optimization but matches the actual mechanics.
  • Tool-side: extend the Task / Agent tool to expand @file macros in sub-agent prompts. (This is a Claude Code change, not deepwork-owned, but worth flagging upstream.)
  • Server-side: have get_review_instructions return the instruction content inline (or an absolute file path the caller is expected to expand) rather than a @file reference that depends on macro semantics that don't exist for sub-agents.

Summary

Both bugs are observable from a clean install of deepwork==0.13.3. They independently block:

  • Bug 1: any review flow that runs from a worktree, especially worktrees outside the parent repo's tree.
  • Bug 2: any review flow that follows the /review skill's documented dispatch handshake without the sub-agent compensating by reading its own instruction file.

In combination, the documented worktree-based review pattern produces sub-agents that receive a literal path string pointing at a file the MCP server wrote in the wrong location. Mitigations exist on the caller side but contradict the skill's documentation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions