Skip to main content
E

dsh-branch-worktree

elfgzp/dsh-branch-worktree

Pick a Git branch, or name a new one, on the New Session screen and start the conversation inside an automatically created worktree, so the current checkout stays untouched.

Install

dsh plugin --profile web add github:elfgzp/dsh-branch-worktree

README

dsh-branch-worktree

English | 简体中文

A DeepSeek Harness (DSH) Web UI plugin that adds a branch picker with automatic Git worktree creation to the New Session screen.

Pick the branch (or type a new branch name) right next to the workspace and agent-mode chips, then let DSH create an isolated checkout for it. The conversation starts inside that checkout, so nothing you have half-edited in your main working copy gets disturbed.

New Session row: workspace chip, branch chip, agent-mode chip

Branch dropdown: pick a branch, name a new one, toggle the worktree

Why

The New Session screen lets you choose a workspace and an agent mode, but a conversation always starts in the workspace directory as it is, on whichever branch is currently checked out. Parallel agent work therefore means juggling branches, stashing changes, or creating worktrees by hand before every session.

This plugin closes that gap with one extra chip:

ControlWhere it comes from
Workspace / projectDSH built-in
Agent mode (e.g. Creator)DSH built-in
Branch, or new branch + basethis plugin
Automatic worktreethis plugin

What it does

  • Sits between the workspace and agent-mode chips — the row reads project → branch → mode, which is exactly the order in which those decisions are made.
  • Opens as a dropdown, not a modal — the composer stays visible and usable behind it.
  • Names itself by default — with the worktree switch already on, the panel opens ready to go: the plugin generates both the branch name and the worktree directory name, previews them, and creates exactly what it previewed. Start a session without typing a branch name at all.
  • Pick an existing branch, or type a new name — turn automatic naming off to choose the branch yourself. A new branch is created from the base you choose (defaults to the repository's own default); an existing branch is reused instead of being recreated.
  • Toggles between two modes:
    • Create a worktree (default) — git worktree add under <repo>/.worktrees/<branch>, then registers that directory as a DSH Workspace and starts the session there. Your current checkout is never touched, so uncommitted work stays exactly where it was.
    • Switch in place — git checkout the branch in the current checkout. Refused when the tree is dirty, so it can never clobber your changes.
  • Branches already checked out elsewhere are shown, not offered — they are disabled in the picker with an in use marker, because Git refuses to check out the same branch twice.
  • The worktree pool stays out of git status — the first run appends .worktrees/ to .git/info/exclude.
  • Unsent draft is carried over — if you already typed a prompt, it moves with you into the new session, and it also seeds the generated name.
  • Not a Git repository? The chip disappears instead of offering a dialog that cannot do anything — and it comes back as soon as you switch to a workspace that is a repository. (A directory outside Git answers the probe with a reduced isRepo: false payload; the client treats that as "hide the chip", never as a repository to render.)

The worktree layout it produces:

my-repo/
├── .git/
├── .worktrees/
│   ├── feature-login/      ← branch feature/login
│   └── fix-crash/          ← branch fix/crash
└── ... your main checkout, untouched

Install

Requires DSH >= 0.2.0 and git on PATH.

  1. Open the sidebar → Plugins → Add plugin → Install a third-party plugin (accepts npm packages, Git repositories, and local directories).

  2. Paste the Git repository address:

    https://github.com/elfgzp/dsh-branch-worktree
    
  3. Confirm, then switch on Branch & Worktree under Installed plugins. Restart DSH if the UI does not pick it up right away.

Ask your agent to install it

In a DSH session with the plugin-manager tool enabled (Creator mode has it), just say:

从 https://github.com/elfgzp/dsh-branch-worktree 安装这个插件并启用

or in English:

Install the plugin at https://github.com/elfgzp/dsh-branch-worktree and enable it

The agent calls plugin_manager with install_bundle, which adds the dependency to your profile, appends the bundle to dsh.profile.bundles, and activates it.

Manual, for development

git clone https://github.com/elfgzp/dsh-branch-worktree
cd dsh-branch-worktree

Then install that absolute directory through Plugins → Add plugin → Install a third-party plugin. The profile records it as a link: dependency, so later edits need only a restart (or a page reload for the browser half).

Never hand-edit the profile's package.json or cordis.patch.yml. The plugin manager owns those files; editing them directly desynchronizes the bundle list.

Usage

  1. Click New Session.
  2. Choose your workspace, then click the branch chip in the middle.
  3. Press Start here straight away — automatic naming and the worktree switch are both on by default, so the branch and worktree names are generated for you.
  4. To name things yourself, turn Auto name off, then pick an existing branch or type a name under New branch — a based on picker then appears.
  5. Press Start here (or hit Enter in the name field).

The session opens in the resulting directory, and the workspace appears in the sidebar under the branch name.

Automatic naming

This is the default: open the panel, press Start, done.

  • Generated branches live under a wt/ namespace, so they are easy to tell apart from branches you named yourself.
  • The name is random and readable: an adjective, a noun, and a short tag — wt/brave-otter-7f3a.
  • It is never derived from your conversation. A worktree name is a durable Git ref that outlives the prompt that happened to be in the box: deriving it from a draft would leak prompt text into branch names, into git log output, and into anything the branch is pushed to — and two unrelated branches would collide on the same sentence. The draft still travels with you into the new session; it just does not name the branch.
  • Collisions are retried automatically against both existing branches and .worktrees/ directory names, including the ones that only collide after the slash is flattened (wt/a-b and wt/a/b both map to wt-a-b).
  • The panel previews the name and path under Will create before you commit, so automatic never means opaque.
  • What is previewed is what is created: the displayed name is sent to the Host verbatim.

Turn Auto name off to get exactly the manual behavior described below.

Moving a running session into a new worktree

The New Session chip only exists while the session is blank. Once a conversation is running there is a second entry point: a thin status bar across the bottom of the window shows the current working directory, and — while that directory is a Git repository that is not already a worktree — a New worktree button.

Clicking it creates a wt/… worktree and moves that same session onto it. No second workspace appears in the sidebar and no second conversation is opened: the Host appends a durable working-directory change to the running conversation, which is the same mechanism the shipped create_worktree tool uses. The model is notified by the Host, so you never have to type "I moved the files, go look there" yourself.

The bar hides itself as soon as the directory is inside .worktrees/, so it stops offering a switch once you are already isolated.

What the worktree toggle actually does

This is the part that is easy to misread: "Create worktree" does not imply "create branch". The rule is:

The branch name you giveWhat runs
Automatic (default)Generates an unused wt/… branch and runs git worktree add -b — creates the branch in a new worktree
Does not existgit worktree add -b <branch> <path> <base> — creates the branch and checks it out in the new worktree
Already existsgit worktree add <path> <branch> — reuses the branch, creating nothing
Checked out in another worktreeRefused, because Git will not check out the same branch twice
Checked out in the main working copyRefused, instead of handing the main checkout back as if it were a worktree

That last row matters: previously, selecting the current branch with the worktree switch on returned the main checkout itself (reusedWorktree: true) — the switch appeared to work while isolating nothing. That combination is now an explicit error, so the switch means what it says.

So a new name starts a new branch, an existing name reuses one, and you never get a surprise duplicate or a silently skipped worktree. The dropdown says which path is about to be taken before you commit to it.

What happens on disk

For a worktree run of branch feature/login in /work/my-repo:

# 1. the pool directory and its exclusion
mkdir -p /work/my-repo/.worktrees
echo '.worktrees/' >> /work/my-repo/.git/info/exclude

# 2. a new branch is created from the chosen base, or the existing branch is reused
git worktree add -b feature/login /work/my-repo/.worktrees/feature-login <base>

# 3. DSH registers /work/my-repo/.worktrees/feature-login as a Workspace

Nothing is ever deleted by this plugin. To retire a worktree:

git worktree remove .worktrees/feature-login
git branch -d feature/login

Errors you may see

MessageMeaning
This workspace is not inside a Git repository.The selected workspace has no .git; the chip would not have been shown if this was known up front.
Branch names accept letters, digits and . _ / - only.The typed branch name is not a valid Git ref.
That branch is already checked out in another worktree.Git will not check out the same branch twice. Pick another branch, or create a new one.
That branch is checked out in your main working copy.The main checkout is not a worktree, so it cannot be isolated. Pick another branch, or turn automatic naming on.
A worktree directory with that name already exists.A stale directory is in the way at .worktrees/<branch>. Remove it with git worktree prune or by hand.
Your current checkout has uncommitted changes.You turned the worktree option off while the tree was dirty. Turn it back on, or commit/stash first.
Git refused the operation.Any other Git failure; the Git message is shown in the dialog.

How it works

┌──────────────────────── Browser (client.js) ────────────────────────┐
│  conversation.hero.options slot occupant                            │
│    button  ──► Modal: branch select / new branch / worktree switch  │
│                        │                                            │
│                        │  POST /branch-worktree/create               │
└────────────────────────┼────────────────────────────────────────────┘
                         ▼
┌──────────────────────── Host (index.js) ────────────────────────────┐
│  connection.requestRejection()  ← trust fence, before anything else │
│  /branch-worktree/inspect  → repo, branches, worktrees, dirty state │
│  /branch-worktree/suggest  → branch + path automatic mode would make│
│  /branch-worktree/create   → git worktree add | git checkout        │
│                            → workspaceRegistry.create(path)         │
└─────────────────────────────────────────────────────────────────────┘
                         │
                         ▼   client calls uiWorkspace.startSession(id)
                  session created with header.cwd = the new checkout

Two details are worth knowing:

  • The session must be created in the worktree, not moved into it. A DSH session's header.cwd is frozen creation metadata: the system prompt's cwd, the shell tool's default workdir, and the sandbox's workspaceRoot all derive from it. The plugin therefore registers the worktree as a Workspace and opens a blank session there, rather than mutating an existing session.
  • All Git work runs on the Host, never in the browser, and always through execFile with a literal argv — no shell interpolation of branch names. Every route validates against the connection trust fence first, so a page from another origin cannot drive Git on your machine.

Where it registers

HalfSurface
HostwebServer exact routes /branch-worktree/inspect, /branch-worktree/suggest, /branch-worktree/create
Clientconversation.hero.options (list slot), id branch-worktree, order -10

On sitting between the workspace and agent-mode chips: DSH ships no slot in that position. conversation.hero.workspace and conversation.hero.agentPreset are display: contents wrappers, and the only slot meant for extra controls — conversation.hero.options — is right-aligned at the end of the row. The plugin therefore injects a small scoped stylesheet that makes that container display: contents and uses order to place itself between the two shipped chips. The selectors are :has()-scoped to the New Session row, so conversation views are untouched, no other plugin's nodes are moved, and the block becomes irrelevant the day DSH grows a real slot there.

It depends on the shipped @deepseek-ai/dsh-client-ui-conversation plugin only, and uses no private APIs beyond the documented webServer, connection, workspaceRegistry, slots, locale, and uiWorkspace services.

Compatibility

DSHStatus
0.2.1-rc.3tested
>= 0.2.0expected to work

Both en and zh locales ship with the plugin; the UI follows the DSH language setting.

Uninstall

Plugins → Branch & Worktree → Remove. Worktrees and branches this plugin created are left on disk; clean them up with the git worktree remove commands shown above.

Prior art

Before writing this, I looked for an existing plugin that covered the whole flow. These are the ones that exist:

RepositoryStarsWhy it was not enough
frederico-kluser/dsh-worktree-jump2Worktree jumping only; no branch picker at session creation
Letter2025/dsh-task-worktree2Task isolation, no project/mode-integrated control
YS-BW/dsh-plugins1General plugin collection
zhang-guo-wen/dsh-worktree0Incomplete
epik7th/dsh-task-worktree0Incomplete
d0nj/dsh-plugins0General plugin collection

None combined project + agent mode + branch + automatic worktree in the New Session row, so this plugin does exactly that and nothing else.

Note that DSH already ships a non-GUI worktree capability — @deepseek-ai/dsh-experimental-worktree with its create_worktree model tool — but it is off by default in every shipped profile and is model-driven rather than part of the New Session flow. This plugin complements it; the two do not conflict.

License

MIT

Related plugins