Skip to content

Worktree actions are offered without a repository, and reach an ancestor repository #55776

Open

Description

@YueSmith

Description

Both come from the same cause: the worktree strategy is git-only and finds its
repository by searching ancestor directories.

  1. Offered without a repository. The web session details menu offers worktree
    actions for a project whose directory holds no repository. Creating one can
    only fail.
  2. Reaching an ancestor repository. A plain directory under a repository runs
    worktree list/create/remove against that repository, because repository
    discovery walks up. The session can end up outside the directory it runs in.

Plugins

None.

OpenCode version

2.0.29

Steps to reproduce

  1. Open a session in a directory that is not a git repository.
  2. Open the session details menu and pick "New worktree". POST /api/worktree
    returns 400.

For the second: git init a parent of the working directory, then list or create
a worktree. The operation targets the parent repository.

Screenshot and/or share link

Attached on the pull request.

Operating System

Debian 13 (Linux container)

Terminal

web UI (also reproducible in the TUI)

Activity

  1. opencode-agent commented on Oct 15, 2026

    @opencode-agent
    Contributor

    Thanks for the clear report. I reproduced both parts on 2.0.29, and they still happen on the latest dev (3c61484). I used the server API, so no model was needed.

    1. No repository: for a session in a plain directory, POST /api/worktree returns HTTP 400 (Worktree source not found: <dir>). On dev, the web session details menu still shows "New worktree" without checking whether the project uses git. I checked this in the code; I didn't open the web UI.
    2. Parent repository: I opened a session in parent/child while it was a plain directory, then ran git init in parent. After a worktree refresh, the plain project's worktree list shows parent. Creating a worktree from it adds a real worktree to the parent repository, and git worktree list there shows it. A directory that is already inside a repository when its session starts joins that repository's project, so there this is expected.

    The script below reproduces both parts. The cause looks like the one you describe: git.repo.discover walks up to the nearest .git, and nothing checks that the repository root it finds is the requested directory. Related: #55215 (the same "offered without a repository" problem in the TUI), PR #55326 (the TUI fix) and your PR #55771.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces #55776 through the server API (no model needed).
    # Usage: bash repro.sh                            # server = `opencode` from PATH
    #        OC_DEV=/path/to/checkout bash repro.sh   # server = CLI run from source
    set -u
    W=$(mktemp -d /tmp/opencode/oc55776.XXXX)
    export HOME="$W/home"; mkdir -p "$HOME"
    export OPENCODE_SERVER_PASSWORD=repro
    PORT=${PORT:-$((4800 + RANDOM % 1000))}
    URL=http://127.0.0.1:$PORT
    if [ -n "${OC_DEV:-}" ]; then
      bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" \
        "$OC_DEV/packages/cli/src/index.ts" serve --port "$PORT" >"$W/serve.log" 2>&1 &
    else
      opencode serve --port "$PORT" >"$W/serve.log" 2>&1 &
    fi
    SERVER=$!
    trap 'kill $SERVER 2>/dev/null' EXIT
    for _ in $(seq 60); do grep -q listening "$W/serve.log" && break; sleep 1; done
    cat "$W/serve.log"
    api() { opencode api --server "$URL" "$@" 2>&1 | grep -v '^Warning: Server'; echo; }
    pid() { grep -o '"projectID":"[^"]*"' | head -1 | cut -d'"' -f4; }
    
    echo "### 1. Plain directory, no repository: create worktree"
    mkdir -p "$W/plain"
    P1=$(api POST /api/session -d "{\"location\":{\"directory\":\"$W/plain\"}}" | pid); echo "project $P1"
    api POST /api/worktree/refresh -d "{\"projectID\":\"$P1\"}"
    api POST /api/worktree -d "{\"projectID\":\"$P1\"}"
    
    echo "### 2. Plain directory whose parent later becomes a git repository"
    mkdir -p "$W/parent/child"
    P2=$(api POST /api/session -d "{\"location\":{\"directory\":\"$W/parent/child\"}}" | pid)
    echo "plain project $P2 (canonical $W/parent/child)"
    (cd "$W/parent" && git init -q && git -c user.email=a@b -c user.name=a commit -q --allow-empty -m init)
    api POST /api/worktree/refresh -d "{\"projectID\":\"$P2\"}"
    echo "worktree list for the plain project (expect nothing outside $W/parent/child):"
    api GET "/api/worktree?projectID=$P2"
    echo "create worktree for the plain project from the listed checkout:"
    api POST /api/worktree -d "{\"projectID\":\"$P2\",\"from\":\"$W/parent\",\"name\":\"wt1\"}"
    echo "git worktree list in the PARENT repository:"
    (cd "$W/parent" && git worktree list)

    Run it: bash repro.sh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions