Skip to content

TUI offers worktree creation for a project with no VCS, then fails with HTTP 400 #55215

Open

Description

@nathwn12

Summary

In the TUI, the worktree affordance is offered for a project whose directory is not a version-controlled repository. Creating one fails with HTTP 400 and Worktree directory unavailable: <dir>. The action can never succeed, and the affordance stays reachable without any keybind.

Environment

  • opencode version: 2.0.28
  • OS: Windows (platform-independent; the defect is in worktree strategy resolution, not in the OS)
  • Terminal: omitted (not relevant to the defect)
  • Shell: omitted (not relevant to the defect)
  • Install/channel: release
  • Active plugins: none required to reproduce; the default git strategy is sufficient

Reproduction

  1. Open (or move a session into) a project directory that is not a git (or hg) repository.
  2. Open the worktree / move-session dialog for that project.
  3. Create a new worktree.
  4. Observe POST /api/worktree returning HTTP 400 with message Worktree directory unavailable: <dir>.

Expected Behavior

For a project with no VCS, the worktree affordance should be hidden, or explained as unavailable, rather than presented as a working action.

Actual Behavior

The dialog offers "new worktree"; the create call returns HTTP 400 Worktree directory unavailable: <dir>.

Additional Context

Root cause (from public source)

The worktree strategy is git-only. Repository discovery finds nothing for a non-repo directory, so create raises DirectoryUnavailableError, and the server maps it to a 400:

  • packages/core/src/worktree/git.ts:18 (create) and :29 (list) - if (!repository) return yield* new DirectoryUnavailableError(...) after git.repo.discover(...).
  • packages/core/src/worktree/directory.ts:14 - canonical raises DirectoryUnavailableError when the path is not a directory.
  • packages/server/src/handlers/worktree.ts:59 - Worktree.DirectoryUnavailableError becomes Worktree directory unavailable: <dir>, surfaced through badRequest for worktree.create (handler registered at packages/server/src/handlers/worktree.ts:17).

Why disabling keybinds is not a workaround

The dialog remains reachable without keyboard shortcuts:

  • Inline literal bindings in the dialog's own bindings array, bypassing the keymap: packages/tui/src/component/dialog-open.tsx:384,410,415 (e.g. ctrl+n -> New worktree).
  • Palette and slash entries: session.move (slash: worktrees) at packages/tui/src/component/prompt/index.tsx:618-627; open.menu (slash: open) at packages/tui/src/app.tsx:763-772.
  • Mouse footer actions rendered through the select dialog footer: packages/tui/src/ui/dialog-select.tsx:626 (FooterAction), used by the workspaces dialog.
  • Automatic worktree list plus refresh fired when the dialog opens: packages/tui/src/component/dialog-open.tsx:121-134 and packages/tui/src/component/dialog-workspaces.tsx:96-107.

The four dialog.move_session.* keybinds (new, move, delete, refresh - packages/tui/src/config/keybind.ts:268-271) can be disabled, but the affordance itself cannot be removed by configuration.

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this on 2.0.28, and it still happens on the latest dev (e9def88).

    Steps, in a plain folder with no .git or .hg:

    1. Run /worktrees. The Worktrees dialog shows "No worktrees available" but still offers new ctrl+a.
    2. Press ctrl+a and enter a name. The prompt footer then shows (new worktree).
    3. Submit a first prompt. A Creating workspace failed toast appears and the session carries on in the original folder.

    One difference: on this path the error was Worktree source not found: <dir>, not Worktree directory unavailable: <dir>. I think your project directory was already registered (for example after a session move), so the request got as far as the git strategy and failed there instead. The underlying problem is the same: worktree creation is offered for a project with no VCS and can never succeed.

    The recording and screenshots below show this, and the script below reproduces it. Related reports:

    recording.mp4

    Screenshot

    Screenshot

    Reproduction script (drive.ts)
    // Repro for anomalyco/opencode#55215: worktree creation is offered in a project
    // with no VCS (no .git/.hg) and fails with "Worktree directory unavailable".
    import { Effect } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        // No `git: true`: the project directory is a plain folder, not a repository.
        project: { files: { "README.md": "plain folder, no VCS\n" } },
        tui: { recording: true, keypressOverlay: true },
      },
      ({ ui, llm }) =>
        Effect.gen(function* () {
          yield* ui.screenshot("01-home")
          yield* ui.type("/worktrees")
          yield* Effect.sleep(500)
          yield* ui.enter()
          yield* Effect.sleep(1500)
          yield* ui.screenshot("02-worktrees-dialog")
          // "new ctrl+a" in the Worktrees dialog footer
          yield* ui.press("a", { ctrl: true })
          yield* Effect.sleep(1000)
          yield* ui.type("demo")
          yield* ui.screenshot("03-new-worktree-name")
          yield* ui.enter()
          yield* Effect.sleep(1000)
          yield* ui.screenshot("03b-pending-new-worktree")
          // With no session yet, worktree creation is deferred until the first prompt is submitted.
          yield* llm.queue(Llm.text("ok"))
          yield* ui.submit("hello")
          yield* ui.waitFor("Creating workspace failed", { timeout: 15000 })
          yield* ui.screenshot("04-error")
          yield* Effect.sleep(1500)
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. drakeo338 commented on Oct 13, 2026

    @drakeo338

    I'm looking at this and plan to open a fix shortly.

  3. drakeo338 commented on Oct 21, 2026

    @drakeo338

    Releasing this one: I won't be opening the PR after all, so anyone is welcome to pick it up.

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