.vibe.toml
The .vibe.toml file contains shared configuration that is typically committed to git and shared with your team.
Copy Configuration
Section titled “Copy Configuration”Copy individual files from the origin repository to the worktree:
[copy]files = [".env", "config.json"]Glob Patterns
Section titled “Glob Patterns”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 |
Prepend and Append
Section titled “Prepend and Append”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.
Directories v0.4.0+
Section titled “Directories ”Copy entire directories recursively:
[copy]dirs = [ "node_modules", # Exact directory path ".cache", # Hidden directories "packages/*" # Glob pattern for multiple directories]Shared Directories (symlink) v3.1.0+
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
symlinkentry that was actually linked takes precedence over every other copy source covering the same path — afiles/dirsentry, and equally a file picked up byuntrackedormodified. The path is linked, not copied. This is applied after glob expansion, sodirs = [".*"]alongsidesymlink = [".cache"]still links.cacheand 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.Cacheand.cacheare 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 startcontinues — the worktree stays usable. Nothing was linked in that case, so afiles/dirsentry 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 cleanremoves the link, never the directory it points at.
Concurrency v0.17.0+
Section titled “Concurrency ”Control the number of parallel directory copy operations:
[copy]concurrency = 8- Default:
4 - Range:
1to32 - Higher values may speed up copying on systems with fast storage (NVMe, SSDs)
- Lower values reduce system resource usage
Environment variable override:
VIBE_COPY_CONCURRENCY=16 vibe start feat/my-featureThe 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
Hooks Configuration
Section titled “Hooks Configuration”See Hooks for details on configuring pre/post hooks.
Submodules Configuration
Section titled “Submodules Configuration”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.tomlwith that submodule’s own trust entry - Runs the submodule config’s hooks and copy rules with paths resolved from the submodule root
--no-hooksskips submodule hooks, but does not skip submodule initialization--no-copyskips submodule copy rules- Submodule update, trust, path validation, or setup failures abort
vibe startand 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 = 30command— 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:1to3600.
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 contract
Section titled “The contract”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.
Limits on the output
Section titled “Limits on the output”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 |
Timeout and failure
Section titled “Timeout and failure”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
HEADchanges (a new commit, a checkout), or - the worktree’s uncommitted changes change (
git statusreports something different), or - the worktree’s branch name changes (
vibe rename), or - the worktree’s base changes (
git branch --set-upstream-to), or [summary] commanditself 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.
Example scripts
Section titled “Example scripts”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.
path_script
Section titled “path_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:
#!/bin/bashecho "${HOME}/worktrees/${VIBE_REPO_NAME}-${VIBE_SANITIZED_BRANCH}"Full Example
Section titled “Full Example”[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"