EnterWorktree ignores worktree.baseRef: "fresh" — still branches from local HEAD (v2.1.144)

Status Fixed / completed
Reported on v2.1.144
Maintainer reply None cached
Activity 5 comments · opened May 19, 2026 · closed Jun 25, 2026

Summary

In Claude Code v2.1.144, EnterWorktree ignores the documented worktree.baseRef: "fresh" default (and explicit setting) and creates the new branch from local HEAD instead of origin/<default-branch>.

Confirmed via reflog: branch: Created from HEAD.

This looks like a regression of the bug that was reported and closed for Agent({ isolation: "worktree" }) in #60235 — same root cause re-surfacing on the EnterWorktree code path. Earlier closed issues #54940 / #39506 / #27134 describe the same direction of bug pre-v2.1.128.

Environment

  • Claude Code: v2.1.144 (Linux ELF, ~/.local/share/claude/versions/2.1.144)
  • Platform: Linux 6.17.0-23-generic
  • ~/.claude/settings.json contains:

``json
"worktree": { "baseRef": "fresh" }
`
Schema-valid (confirmed against the binary's published JSONSchema). Per the v2.1.133 changelog (referenced in #57148),
fresh = branch from origin/<default-branch>` is the documented default.

Reproduce

In any git repo with origin/<default-branch> configured, with the session checked out on a feature branch ahead of origin/<default>:

# pre-state
$ git symbolic-ref refs/remotes/origin/HEAD   # refs/remotes/origin/master
$ git rev-parse --abbrev-ref HEAD             # feature-branch  (not master)
$ git rev-parse HEAD                          # <SHA-A>
$ git rev-parse origin/master                 # <SHA-B>          (different SHA)

Then in a Claude Code session, invoke:

EnterWorktree(name="test-baseref")

Inside the new worktree:

$ git rev-parse HEAD
<SHA-A>                                       # ← matches local HEAD, not origin/master

$ git reflog show worktree-test-baseref
<SHA-A> worktree-test-baseref@{0}: branch: Created from HEAD

Expected

Per the JSONSchema description:

worktree.baseRef: 'fresh' (default) branches from origin/<default-branch> for a clean tree.

New branch should be at origin/master (<SHA-B>), reflog should show Created from origin/master.

Actual

New branch is at local HEAD. Reflog shows Created from HEAD. The harness appears to be invoking git worktree add -b <branch> <path> with no explicit base ref.

Impact

Same impact pattern as #54940 / #39506 / #27134 in reverse: local-only commits on the current feature branch silently leak into every new worktree. When that worktree is later pushed as a PR, the diff contains commits not authored in this session — triggering force-push cleanup and stale reviewer comments. The fresh setting was specifically designed to prevent this, and it's not taking effect.

Suggested fix

Make EnterWorktree's worktree-creation path honor worktree.baseRef. The fix that landed for Agent({ isolation: "worktree" }) in #60235 likely just needs to be applied to the EnterWorktree code path as well.

Workaround

None via config. Users must manually git fetch and check the reflog of every worktree branch before pushing — which defeats the purpose of the setting.

View original on GitHub ↗

4 Comments

jshaofa-ui · 3 months ago

Fix: EnterWorktree Ignores worktree.baseRef: "fresh"

Root Cause Analysis

In Claude Code v2.1.144, EnterWorktree creates new branches from local HEAD instead of origin/<default-branch> when worktree.baseRef: "fresh" is configured. The reflog confirms: branch: Created from HEAD.

Why This Is a Regression

This is the same root cause that was reported and fixed for Agent({ isolation: "worktree" }) in #60235, now re-surfacing on the EnterWorktree code path. Earlier closed issues #54940 / #39506 / #27134 describe the same direction of bug pre-v2.1.128.

Root Cause

The EnterWorktree tool handler likely:

  1. Reads worktree.baseRef from settings.json
  2. Passes it to the worktree creation logic
  3. But the worktree creation logic only applies baseRef for Agent-initiated worktrees, not for EnterWorktree-initiated ones

The code path divergence is:

Agent({ isolation: "worktree" }) → WorktreeManager.createIsolatedWorktree()
  → reads baseRef from settings → branches from origin/<default-branch> ✓

EnterWorktree(name) → WorktreeManager.createManualWorktree()
  → uses git worktree add -b <name> HEAD  ← BUG: always uses HEAD

Evidence

  • Settings: "worktree": { "baseRef": "fresh" } (schema-valid, confirmed)
  • Pre-state: Feature branch ahead of origin/master (<SHA-A> vs <SHA-B>)
  • Result: New worktree HEAD = <SHA-A> (local HEAD), not <SHA-B> (origin/master)
  • Reflog: branch: Created from HEAD

---

Proposed Fix

Fix: Pass baseRef Through EnterWorktree Code Path

// In src/tools/worktree/enter-worktree.ts (or equivalent):

interface EnterWorktreeParams {
  name: string;
  baseRef?: string; // Add this
}

async function handleEnterWorktree(params: EnterWorktreeParams): Promise<WorktreeResult> {
  // Read settings
  const settings = await loadSettings();
  const worktreeSettings = settings.worktree || {};
  
  // Use explicit param, then settings, then default
  const baseRef = params.baseRef ?? worktreeSettings.baseRef ?? 'fresh';
  
  // Determine the correct starting point
  let startPoint: string;
  if (baseRef === 'fresh') {
    // Branch from origin/<default-branch>
    const defaultBranch = await getDefaultBranch();
    startPoint = `origin/${defaultBranch}`;
  } else if (baseRef === 'head') {
    // Branch from local HEAD (current behavior)
    startPoint = 'HEAD';
  } else {
    // Branch from explicit ref
    startPoint = baseRef;
  }
  
  // Create worktree with correct starting point
  const worktreePath = await createWorktree(params.name, startPoint);
  
  return { path: worktreePath, baseRef, startPoint };
}

async function getDefaultBranch(): Promise<string> {
  // Method 1: git symbolic-ref refs/remotes/origin/HEAD
  try {
    const result = await execGit('symbolic-ref', 'refs/remotes/origin/HEAD');
    // Output: refs/remotes/origin/master
    return result.trim().split('/').pop()!;
  } catch {
    // Method 2: git remote show origin
    const showResult = await execGit('remote', 'show', 'origin');
    const match = showResult.match(/HEAD branch: (\w+)/);
    if (match) return match[1];
  }
  // Method 3: Default to 'main' or 'master'
  return 'main';
}

Fix: Ensure Agent and EnterWorktree Share the Same Code Path

// In src/tools/worktree/worktree-manager.ts:

class WorktreeManager {
  async createWorktree(
    name: string,
    options: { baseRef?: string; isolation?: string } = {}
  ): Promise<WorktreeResult> {
    const settings = await loadSettings();
    const worktreeSettings = settings.worktree || {};
    
    // Unified baseRef resolution — same for Agent and EnterWorktree
    const baseRef = options.baseRef ?? worktreeSettings.baseRef ?? 'fresh';
    const startPoint = await resolveBaseRef(baseRef);
    
    // Single code path for worktree creation
    return this._doCreateWorktree(name, startPoint, options);
  }
  
  private async resolveBaseRef(baseRef: string): Promise<string> {
    switch (baseRef) {
      case 'fresh': {
        const defaultBranch = await getDefaultBranch();
        return `origin/${defaultBranch}`;
      }
      case 'head':
        return 'HEAD';
      default:
        return baseRef;
    }
  }
}

Fix: Add Validation and User Feedback

// In the EnterWorktree tool definition:

const EnterWorktreeTool = {
  name: 'EnterWorktree',
  description: 'Create and enter a new git worktree',
  parameters: {
    name: { type: 'string', description: 'Worktree name' },
    baseRef: {
      type: 'string',
      enum: ['fresh', 'head'],
      description: 'Branch source: "fresh" = origin/<default>, "head" = local HEAD. Defaults to worktree.baseRef setting.',
    },
  },
  // In the tool response, include the actual starting point:
  async execute(params: EnterWorktreeParams) {
    const result = await handleEnterWorktree(params);
    return {
      path: result.path,
      baseRef: result.baseRef,
      startPoint: result.startPoint,
      message: `Created worktree from ${result.startPoint}`,
    };
  },
};

---

Files to Modify

| File | Change |
|------|--------|
| src/tools/worktree/enter-worktree.ts | Add baseRef resolution to EnterWorktree handler |
| src/tools/worktree/worktree-manager.ts | Unify Agent/EnterWorktree code paths |
| src/tools/worktree/resolve-base-ref.ts | New: shared baseRef resolution utility |

---

Testing Plan

  1. Unit test: resolveBaseRef('fresh') returns origin/<default-branch>
  2. Integration test: EnterWorktree creates branch from origin/master when on feature branch
  3. Regression test: Agent({ isolation: "worktree" }) still works correctly
  4. Settings test: Explicit baseRef param overrides settings value

---

Estimated Impact

  • Users affected: All users who use EnterWorktree with worktree.baseRef: "fresh"
  • Fix complexity: Low (~100 lines, primarily routing fix)
  • Risk: Low (isolated to worktree creation, existing Agent path unchanged)
  • Value: Fixes a regression that causes data integrity issues — worktrees created from wrong base can lead to incorrect merges, lost changes, and confusion
mickesgit · 2 months ago

This is a huge problem and I hope it gets some attention.

tomatemeo · 2 months ago

Just chiming in here to mention that git generally also allows to rename remotes, I ran into an issue because my remote is gitlab/master, not origin/master, obtaining this dynamically from the git config, instead of hard coding it, would help a lot.

kthhrv · 2 months ago

Verified fixed on Claude Code 2.1.191 (this was filed against 2.1.144).

Re-ran the original repro against the current build:

  • Repo on a feature branch whose HEAD is ahead of origin/<default-branch>, with ~/.claude/settings.json containing "worktree": { "baseRef": "fresh" }.
  • EnterWorktree now creates the new branch from origin/<default-branch>, not local HEAD.

Reflog shows the expected:

<sha> worktree-<name>@{0}: branch: Created from origin/main

(was Created from HEAD on 2.1.144). So worktree.baseRef: "fresh" is honored as documented and the "local commits leak into the worktree/PR" failure mode is resolved. Closing.

Two adjacent notes for anyone landing here:

  • The remote-name case raised above (remote not named origin, e.g. gitlab/master) is a separate concern — fresh resolves origin/<default> specifically. Worth its own issue rather than tracking under this one.
  • fresh branches from the cached origin/<default> (the remote-tracking ref as of your last fetch); it does not git fetch first. Run a fetch beforehand if you need the branch at the live remote tip. Not a regression — just worth knowing.

Showing cached comments. Read the full discussion on GitHub ↗