Git worktrees and AI coding sessions in one picker for Neovim.
Lists the worktrees of the current repository alongside the AI sessions that ran in them, previews what each session was about, and switches your whole workspace to the one you pick.
- Worktree-centric picker — branch, dirty state, ahead/behind, session count
- Session recap in the preview — title, where you left off, files touched
- Full workspace switch — buffers, session layout, LSP roots and the file tree all follow, so no stale buffers from another worktree
- Safe delete — reports uncommitted changes and unmerged commits before removing
With lazy.nvim:
{
"werdin/switchyard.nvim",
dependencies = { "folke/snacks.nvim", "folke/persistence.nvim" },
opts = {
worktree_dir = ".claude/worktrees",
},
keys = {
{ "<leader>gw", "<cmd>Switchyard<cr>", desc = "Worktrees & sessions" },
},
}opts is passed to require("switchyard").setup(); :Switchyard opens the worktree
picker for the repository containing the current directory.
In the worktree picker:
| Key | Action |
|---|---|
<CR> |
switch the workspace to this worktree |
<C-s> |
list the sessions of this worktree |
<C-n> |
create a worktree from a branch |
<C-d> |
delete this worktree, after a report of what is lost |
<C-y> |
copy the worktree path |
In the session list: <CR> switches the workspace to the session's worktree and resumes
that session in a terminal; <C-o> goes back to the worktree list.
Defaults, passed to setup():
require("switchyard").setup({
worktree_dir = ".claude/worktrees",
providers = { "claude" },
claude = { cmd = { "claude" } },
})worktree_dir— path, relative to the repository root, where<C-n>creates new worktrees (<worktree_dir>/<branch-slug>).providers— names of the session providers to enable. Each listed name isrequired asswitchyard.providers.<name>; an unknown or misconfigured provider is skipped with a warning instead of abortingsetup().claude— options forwarded to theclaudeprovider:claude.cmd— base command used to resume a session (--resume <id>is appended). Override this to point at a wrapper script or a non-default binary.
Two User autocmd patterns fire around a workspace switch, for other plugins
(lualine, tabby, lazygit, …) to hook:
| Pattern | When |
|---|---|
SwitchyardLeavePre |
before the current workspace is torn down |
SwitchyardEnter |
after the new worktree is active; data.path holds the path |
vim.api.nvim_create_autocmd("User", {
pattern = "SwitchyardEnter",
callback = function(args)
print("switched to " .. args.data.path)
end,
})Picking a worktree is a full workspace replacement, not a :cd. In order:
- If any buffer has unsaved changes, prompt to save, discard, or cancel. Cancelling aborts the switch and leaves the workspace untouched.
- Save the current persistence.nvim session (if there is anything worth saving).
- Change directory to the target worktree.
- Close every buffer.
- Restore that worktree's own persistence.nvim session.
- Restart LSP clients, so they relaunch with the new root and attach correctly.
Buffers from the previous worktree do not survive the switch — this is deliberate.
Worktree paths differ from the main repository by one path segment
(.claude/worktrees/<branch>), so a picker or buffer list mixing files from two
worktrees makes it easy to edit the wrong copy of a file without noticing. Each
worktree gets its own persistence.nvim session (keyed by getcwd()), so nothing is
lost — the previous worktree's buffers are exactly as you left them next time you
switch back to it.
- Neovim >= 0.10 (uses
vim.uv,vim.system,vim.lsp.get_clients) - snacks.nvim (picker)
- persistence.nvim (workspace switching)
- git >= 2.30
neo-tree.nvim is optional: when it is
loaded, its root follows a switch. Run :checkhealth switchyard to verify the setup.
:help switchyard after installation, or doc/switchyard.txt here.
docs/design.md covers the full design and the reasoning behind it.
make test # plenary specs, headless
make lint # luacheck
make format # styluaAll three run in CI on every push and pull request.