Skip to content

.vibe.toml

The .vibe.toml file contains shared configuration that is typically committed to git and shared with your team.

Copy individual files from the origin repository to the worktree:

[copy]
files = [".env", "config.json"]

The files array supports glob patterns for flexible file selection:

[copy]
files = [
"*.env", # All .env files in root
"**/*.json", # All JSON files recursively
"config/*.txt", # All .txt files in config/
".env.production" # Exact paths still work
]

Supported patterns:

Pattern Description
* Matches any characters except /
** Matches any characters including / (recursive)
? Matches any single character
[abc] Matches any character in brackets

files_prepend and files_append (and the equivalent _prepend / _append variants of dirs, copy.symlink and every [hooks] array) are valid inside .vibe.toml itself, not only in .vibe.local.toml. Within a single file the effective array is prepend + field + append:

[copy]
files = [".env"]
files_append = [".env.local"]
# Effective: [".env", ".env.local"]

See .vibe.local.toml for how these fields interact across the two files.

Copy entire directories recursively:

[copy]
dirs = [
"node_modules", # Exact directory path
".cache", # Hidden directories
"packages/*" # Glob pattern for multiple directories
]
Section titled “Shared Directories (symlink) ”

Instead of copying a directory into every worktree, point the worktree at the one in the origin repository with a symlink:

[copy]
dirs = ["node_modules"]
symlink = [".cache", ".turbo"]

Why: Copy-on-Write cloning makes dirs fast on APFS/Btrfs/XFS, but on a filesystem without reflink support (or on Windows) a full copy of a large dependency or cache tree is slow and wastes disk. Some directories also do not need per-worktree isolation at all — a shared build or download cache is fine, or even preferable.

Rules:

  • Entries are exact directory paths relative to the repository root. Glob patterns are not supported (a symlink names one directory to share).
  • A symlink entry that was actually linked takes precedence over every other copy source covering the same path — a files/dirs entry, and equally a file picked up by untracked or modified. The path is linked, not copied. This is applied after glob expansion, so dirs = [".*"] alongside symlink = [".cache"] still links .cache and copies only the other matches. Paths under a shared directory (and its parents) are excluded too, so a copy can never be written through the link into the origin repository. On a case-insensitive filesystem (APFS, NTFS) the exclusion ignores case, because .Cache and .cache are the same directory entry there.
  • The target must exist in the origin repository and stay inside it. A missing target, a path escaping the repository, or an OS refusal to create the link (Windows without Developer Mode) prints a warning and vibe start continues — the worktree stays usable. Nothing was linked in that case, so a files/dirs entry naming the same path is still copied as usual.
  • An existing real file or directory at that path in the worktree is never replaced; only a stale symlink is refreshed.
  • vibe clean removes the link, never the directory it points at.

Control the number of parallel directory copy operations:

[copy]
concurrency = 8
  • Default: 4
  • Range: 1 to 32
  • Higher values may speed up copying on systems with fast storage (NVMe, SSDs)
  • Lower values reduce system resource usage

Environment variable override:

Terminal window
VIBE_COPY_CONCURRENCY=16 vibe start feat/my-feature

The environment variable takes precedence over the config file setting.

Copy Performance Optimization v0.4.0+

Section titled “Copy Performance Optimization ”

vibe automatically selects the best copy strategy based on your system:

Strategy When Used Platform
Clone (CoW) Directory copy on APFS macOS
Clone (reflink) Directory copy on Btrfs/XFS Linux
rsync Directory copy when clone unavailable macOS/Linux
robocopy (/MT) Directory copy Windows
Standard File copy, or fallback All

How it works:

  • File copy: Always uses native copyFile() for best single-file performance
  • Directory copy: Automatically uses the fastest available method

Benefits:

  • Copy-on-Write is extremely fast as it only copies metadata, not actual data
  • No configuration needed - the best strategy is auto-detected
  • Automatic fallback ensures copying always works

See Hooks for details on configuring pre/post hooks.

Load trusted .vibe.toml files from selected direct submodules after the worktree is created and before the parent repository’s pre_start hooks run:

[submodules]
configs = ["libs/foo", "vendor/bar"]
  • Default: []
  • Each entry must exactly match a direct submodule path in .gitmodules
  • Runs git submodule update --init -- <paths> in the new worktree
  • Loads each submodule’s .vibe.toml / .vibe.local.toml with that submodule’s own trust entry
  • Runs the submodule config’s hooks and copy rules with paths resolved from the submodule root
  • --no-hooks skips submodule hooks, but does not skip submodule initialization
  • --no-copy skips submodule copy rules
  • Submodule update, trust, path validation, or setup failures abort vibe start and leave the created worktree available for inspection

Summary Configuration v3.2.0+

Section titled “Summary Configuration ”

Add a SUMMARY column to vibe list, filled in by a command you provide:

[summary]
command = "./examples/summary/last-commit.sh"
timeout_seconds = 30
  • command — a shell command line. Required for the column to appear at all.
  • timeout_seconds — how long the command may run before it is killed. Default: 30. Range: 1 to 3600.

The column exists whenever command is set, whether or not the command answers for a given worktree. A worktree it stays silent about simply shows an empty cell.

The command is run once per vibe list, from the main worktree, with the batch of worktrees on stdin:

{
"worktrees": [
{
"name": "feat/login",
"path": "/abs/path/to/wt",
"base": "develop",
"head": "0f1e2d3c…"
}
]
}

name and path are always strings; base and head are null when unknown. Only worktrees whose summary is not already cached appear here.

It must print a JSON object on stdout, mapping name to the summary text:

{ "feat/login": "Adds the login form" }

A name the command omits gets no summary and is not cached, so the next run asks about it again.

One batch, not one call per worktree: the interesting summary commands are LLM calls and repository-wide queries, where N invocations cost N times the latency. A single call also lets the command answer comparatively across the whole set.

Duplicate names are skipped: answers are keyed by name, and two detached-HEAD worktrees can share a directory basename. When a batch contains a duplicate, those worktrees are excluded from the request entirely (reported under --verbose) — a missing summary is safer than one shown on the wrong row.

The command’s stdout is untrusted input and is bounded before anything is stored or displayed:

Limit Behaviour
1 MiB of stdout Reading stops at the cap; the over-long answer is rejected
64 KiB of stderr Reading stops at the cap (only the first line is ever quoted)
4 entries per worktree An answer far larger than the request is rejected
JSON object of strings An array, a number or a nested object is a contract violation
First line only A multi-line summary is cut at the first line break
500 characters Longer text is truncated with
Terminal control chars Neutralized before display, exactly as branch names are

If the command exits non-zero, cannot be spawned, times out, or prints something that violates the contract, vibe list warns and keeps going. Any previously cached summary is shown instead of a blank cell, since a slightly stale answer carries more than nothing. In --json mode the warning is withheld so the payload stays parseable.

Summaries are cached per repository at $XDG_CACHE_HOME/vibe/summaries/ (or $HOME/.cache/vibe/summaries/ when XDG_CACHE_HOME is unset or malformed). A second vibe list over an unchanged repository does not run the command at all.

A cached summary is invalidated when:

  • the worktree’s HEAD changes (a new commit, a checkout), or
  • the worktree’s uncommitted changes change (git status reports something different), or
  • the worktree’s branch name changes (vibe rename), or
  • the worktree’s base changes (git branch --set-upstream-to), or
  • [summary] command itself changes — which discards every entry, since a summary produced by the old command says nothing about what the new one means.

timeout_seconds is not part of the invalidation: it changes how long vibe waits, not what the answer is. Entries for worktrees that no longer exist are pruned on every run, and a corrupt or unreadable cache file simply regenerates.

Ready-to-use scripts live in examples/summary/ (Unix; each carries its usage and a [summary] snippet in its header):

Script What the column shows Requires
last-commit.sh Each worktree’s latest commit subject jq, git
note-file.sh The first line of each worktree’s .vibe/note.txt jq
claude.sh An LLM-written description of what each worktree is doing jq, git, claude

Worktree Configuration v0.6.0+

Section titled “Worktree Configuration ”

Customize the worktree directory path using an external script.

Specify a script that outputs the worktree path:

[worktree]
path_script = "~/.config/vibe/worktree-path.sh"

The script receives these environment variables:

Variable Description Example
VIBE_REPO_NAME Repository name my-project
VIBE_BRANCH_NAME Branch name feat/new-feature
VIBE_SANITIZED_BRANCH Sanitized branch name (/-) feat-new-feature
VIBE_REPO_ROOT Repository root path /path/to/repo

Example script:

~/.config/vibe/worktree-path.sh
#!/bin/bash
echo "${HOME}/worktrees/${VIBE_REPO_NAME}-${VIBE_SANITIZED_BRANCH}"
[copy]
files = [
".env",
".env.local",
"**/*.secret"
]
dirs = [
"node_modules",
".cache",
"vendor"
]
concurrency = 8
[hooks]
pre_start = ["echo 'Preparing worktree...'"]
post_start = [
"pnpm install",
"pnpm db:migrate",
"pnpm build"
]
pre_clean = ["git stash"]
post_clean = ["echo 'Cleanup complete'"]
[submodules]
configs = ["libs/foo"]
[summary]
command = "./examples/summary/last-commit.sh"
timeout_seconds = 30
[worktree]
path_script = "~/.config/vibe/worktree-path.sh"