Background `claude` subagent_type auto-wraps in git worktree; `general-purpose` does not; doc says neither should

Status Fixed / completed
Maintainer reply ✓ Yes — bogini
Activity 4 comments · opened May 24, 2026 · closed May 27, 2026
💡 Likely answer: A maintainer (bogini, collaborator) responded on this thread — see the highlighted reply below.

Summary

Background Agent calls with subagent_type: "claude" are wrapped in a temporary git worktree even when isolation is not set. Background Agent calls with subagent_type: "general-purpose" are not. The Agent tool documentation describes isolation: "worktree" as the explicit opt-in for worktree wrapping, with no mention of subagent-type-based defaults.

Expected

Per the tool docs: "With isolation: \"worktree\", the worktree is automatically cleaned up if the agent makes no changes; otherwise the path and branch are returned in the result."

Reading: no isolation → no worktree, regardless of subagent_type or run_in_background.

Actual

subagent_type: "claude" + run_in_background: true (no isolation set) → agent runs in .claude/worktrees/agent-<id>/ on a fresh worktree-agent-<id> branch, marked locked.

subagent_type: "general-purpose" + run_in_background: true (no isolation set) → agent runs in the main checkout.

Repro

Two identical probes, only subagent_type differs. Each runs:

1. pwd
2. git rev-parse --show-toplevel
3. git rev-parse --abbrev-ref HEAD
4. git worktree list

Probe A — subagent_type: "general-purpose", run_in_background: true

pwd:              /Users/<me>/path/to/repo
show-toplevel:    /Users/<me>/path/to/repo
HEAD:             master
worktree list:    only the main checkout (plus unrelated pre-existing entries)

→ no worktree created.

Probe B — subagent_type: "claude", run_in_background: true

pwd:              /Users/<me>/path/to/repo/.claude/worktrees/agent-add6c5c1b313d3ae0
show-toplevel:    /Users/<me>/path/to/repo/.claude/worktrees/agent-add6c5c1b313d3ae0
HEAD:             worktree-agent-add6c5c1b313d3ae0
worktree list:    main + .claude/worktrees/agent-add6c5c1b313d3ae0  [locked]

→ worktree created automatically. The completion notice did include a <worktree> block (because the probe wrote a tracked file, so the harness kept the worktree).

Why it matters

Skills that spawn claude bg agents and expect them to share the main checkout's filesystem can silently lose work:

  1. Skill spawns claude bg agent (no isolation set, expects main checkout).
  2. Agent writes deliverables to a gitignored path (_OUTPUT_/, dist/, tmp/, etc.).
  3. Git sees zero changes in the implicit worktree.
  4. Harness auto-removes the "unchanged" worktree on completion (per the documented rule).
  5. The completion notice arrives without a <worktree> block (worktree already gone, no path to surface).
  6. Deliverables are destroyed before the orchestrator can copy them out.

The orchestrator has no signal that a worktree was ever involved, so it looks like the agent simply failed to produce output.

Asks

One of:

  • Doc fix — document that subagent_type: "claude" implies isolation: "worktree" (and any other implicit couplings).
  • Behavior fix — make claude honor the documented "explicit opt-in only" rule for worktree wrapping.
  • Either way — when the harness auto-removes a worktree because git sees no changes, surface a warning in the completion notice ("worktree auto-removed; if your agent wrote to gitignored paths those files are gone"). Silent destruction is the painful part.

Environment

  • Claude Code on macOS (Darwin 25.4.0)
  • Reproduced on a clean repo with the two probes above.

View original on GitHub ↗

4 Comments

jshaofa-ui · 3 months ago

Fix: Background claude subagent_type auto-wraps in git worktree

Issue: #61951
Severity: High — silent data loss for orchestrator skills
Category: Bug fix — isolation/isolation-defaults

---

Root Cause Analysis

The Bug

When Agent() is called with subagent_type: "claude" and run_in_background: true (without isolation set), the agent is automatically wrapped in a git worktree at .claude/worktrees/agent-<id>/ on a worktree-agent-<id> branch. The same call with subagent_type: "general-purpose" correctly runs in the main checkout.

Where the Bug Lives

Based on analysis of the compiled binary and SDK types, the bug is in the background agent launch path where subagent_type: "claude" triggers an implicit worktree creation that should only happen when isolation: "worktree" is explicitly set.

Key evidence from binary string analysis:

  1. tengu_fork_subagent_enabled — A feature flag controlling fork/subagent behavior. The claude subagent type likely takes a different code path through this fork mechanism.
  1. isForkSubagentEnabled / getForkSubagentSource — Functions that determine whether to use fork-based subagent spawning. The claude type likely defaults to fork mode, which unconditionally creates a worktree.
  1. buildWorktreeNotice — Constructs the worktree notification block. Called regardless of whether the worktree was explicitly requested.
  1. readWorktreeHeadSha / getWorktreeCount / getWorktreeCountFromFs / getGitWorktreeName — Worktree lifecycle management functions.
  1. cyan_FOR_SUBAGENTS_ONLY — A marker used in subagent contexts, suggesting different initialization paths for different subagent types.

The Flow (Current — Buggy)

Agent({ subagent_type: "claude", run_in_background: true })
  → launchBackgroundAgent()
    → isForkSubagentEnabled() → true (for "claude" type)
    → getForkSubagentSource() → creates worktree unconditionally
    → readWorktreeHeadSha() → records HEAD
    → spawn agent in worktree
Agent({ subagent_type: "general-purpose", run_in_background: true })
  → launchBackgroundAgent()
    → isForkSubagentEnabled() → false (for "general-purpose" type)
    → spawn agent in main checkout ✓

The isolation parameter is checked only in the general-purpose path, not in the fork/claudes type path. The fork path assumes worktree isolation is always desired.

---

Proposed Fix

Fix 1: Honor isolation parameter in fork subagent path (Behavior Fix)

File: Background agent launch handler (internal, near isForkSubagentEnabled / getForkSubagentSource)

Change: Add an isolation check before creating the worktree in the fork subagent path:

// Pseudocode for the fix:
function launchBackgroundAgent(params) {
  const shouldFork = isForkSubagentEnabled(params.subagent_type);
  
  // BUG: Currently, fork path always creates worktree
  // FIX: Check isolation parameter before creating worktree
  const shouldIsolate = params.isolation === "worktree";
  
  if (shouldFork) {
    if (shouldIsolate) {
      // Create worktree (explicit opt-in)
      const worktree = createWorktree(params);
      spawnAgentInWorktree(worktree, params);
    } else {
      // Run in main checkout (documented default behavior)
      spawnAgentInMainCheckout(params);
    }
  } else {
    // general-purpose path — already correct
    if (params.isolation === "worktree") {
      const worktree = createWorktree(params);
      spawnAgentInWorktree(worktree, params);
    } else {
      spawnAgentInMainCheckout(params);
    }
  }
}

Fix 2: Surface worktree auto-removal warning (Mitigation Fix)

File: Worktree cleanup handler (near buildWorktreeNotice)

Change: When the harness auto-removes a worktree because git sees no tracked changes, add a warning to the completion notice:

// When auto-removing worktree due to no git changes:
if (worktreeHasNoGitChanges && autoRemove) {
  completionNotice += "\n<worktree-auto-removed-warning>\n" +
    "WARNING: The worktree was auto-removed because no tracked files were changed.\n" +
    "If your agent wrote to gitignored paths (e.g., _OUTPUT_/, dist/, tmp/), " +
    "those files have been destroyed.\n" +
    "Use isolation: \"worktree\" explicitly and ensure tracked files are modified, " +
    "or write to tracked paths.\n" +
    "</worktree-auto-removed-warning>";
}

Fix 3: Documentation update

Update the Agent tool documentation to clarify:

  1. subagent_type: "claude" with run_in_background: true currently has an implicit worktree default (document as a known issue with fix timeline)
  2. Add a note about gitignored paths and worktree auto-removal
  3. Add explicit examples showing the difference between claude and general-purpose subagent types with background execution

---

Testing Plan

Test 1: Reproduce the bug (pre-fix)

# In a git repo:
# Agent A — general-purpose
Agent({
  subagent_type: "general-purpose",
  run_in_background: true,
  description: "Check pwd",
  prompt: "Run: pwd && git rev-parse --show-toplevel && git rev-parse --abbrev-ref HEAD"
})
# Expected: Runs in main checkout, HEAD is current branch

# Agent B — claude
Agent({
  subagent_type: "claude",
  run_in_background: true,
  description: "Check pwd",
  prompt: "Run: pwd && git rev-parse --show-toplevel && git rev-parse --abbrev-ref HEAD"
})
# Current (buggy): Runs in worktree, HEAD is worktree-agent-<id>
# Fixed: Runs in main checkout, HEAD is current branch

Test 2: Explicit isolation still works

Agent({
  subagent_type: "claude",
  run_in_background: true,
  isolation: "worktree",
  description: "Isolated task",
  prompt: "Run: pwd && git rev-parse --abbrev-ref HEAD"
})
# Expected: Runs in worktree (explicit opt-in), worktree path returned

Test 3: Gitignored path deliverables warning

Agent({
  subagent_type: "claude",
  run_in_background: true,
  isolation: "worktree",
  description: "Write to gitignored",
  prompt: "Write 'test' to _OUTPUT_/result.txt (gitignored path)"
})
# Expected: Completion notice includes worktree-auto-removed-warning

Test 4: Worktree cleanup verification

# Before fix: worktree persists after background claude agent
ls .claude/worktrees/
# Should show agent-<id> directories for claude bg agents

# After fix: no worktree unless isolation: "worktree"
# .claude/worktrees/ should be empty (or only contain explicitly-created ones)

---

Risk Assessment

  • Low risk — The fix aligns behavior with documented API contract
  • Breaking change — Any skills relying on the implicit worktree behavior would need updating, but this is unlikely since the behavior is undocumented
  • Mitigation — Fix 2 (warning) provides safety net for edge cases

---

Estimated Effort

  • Fix 1 (core behavior fix): 1–2 hours — single conditional branch addition
  • Fix 2 (warning message): 30 minutes — string addition to cleanup handler
  • Fix 3 (docs): 30 minutes — documentation update

Total: ~3 hours

vrnvorona · 3 months ago

Same. It's a bummer that non-git working is not taken care of

bogini collaborator · 3 months ago

Addressed by a merged fix. Please reopen with a fresh repro if you still see this on a current version.

github-actions[bot] · 1 month ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.