Skip to main content

Worktree Indexes

When you create a git worktree to work on a branch, the new checkout has no TeaRAGs index — searching it would mean re-indexing the whole codebase from scratch, even though the branch differs from the source by only a handful of files. The tea-rags worktree command family solves this: it clones the source project's on-disk index into a new per-worktree collection at near-zero seeding cost, registered under its own alias. You then index only the live diff.

A worktree collection is an isolated, independent snapshot of the source index that lives alongside it. Reindex it as the branch diverges; remove it when the branch is done — the source index is never touched.

There are two ways to get such a collection:

  • Automatically, on the first index-codebase / index_codebase of the worktree path, when another working tree of the same repository is already indexed with the same settings — see Automatic Seeding on First Index.
  • Explicitly, with tea-rags worktree create, which also registers an alias with worktree provenance and can create the git worktree itself — see Commands.
Distinct from project registration

A worktree clone is seeded from an already-indexed source project. To index a brand-new, never-indexed project under an alias instead, use index-codebase --name.

The Footprint​

Every TeaRAGs collection has an on-disk footprint — six artifacts under $TEA_RAGS_DATA_DIR (default ~/.tea-rags/). worktree create clones each one except the indexing lock:

ArtifactLocationClone method
Qdrant vectorsqdrant/storage/collections/<col>_vN/snapshot → recover (online, safe)
Codegraphcodegraph/<col>.duckdbfile copy (after daemon checkpoint)
File-hash snapshotsnapshots/<col>/ (sharded)copy shards + rewrite codebasePath
Stats cachesnapshots/<col>.stats.jsonfile copy
Quarantinesnapshots/<col>.quarantine.jsonfile copy
Indexing locksnapshots/<col>.indexing.locknever cloned — exists only while an index operation runs

The indexing lock is how two processes on one machine avoid indexing the same collection at once. Teardown removes it only when the run that wrote it is dead; a live run's lock is left in place.

Qdrant uses snapshot → recover rather than a cold file copy: the embedded Qdrant daemon is shared and refcounted, so copying its mmap'd segments while it has them open would risk inconsistency. Snapshot → recover is the only safe file-level path that doesn't require restarting the daemon (which would interrupt parallel worktree sessions).

The clone is atomic: artifacts are cloned in order, and the registry entry is written only after every clone succeeds. If any clone fails, the already-cloned artifacts are removed in reverse order and the command aborts — no orphaned state, no half-registered collection.

Provenance​

Worktree collections are marked in the registry with two extra fields so they are discoverable and removable as a class:

  • worktreeOf — the source collection's name (e.g. code_proj), marking this entry as a clone.
  • worktreeName — the worktree name, used by worktree remove <name> to locate the entry.

These fields are additive — code that reads the registry continues to work unchanged. worktree list filters on worktreeOf, and worktree remove refuses any entry that lacks it (you cannot accidentally tear down a real project through this command).

Automatic Seeding on First Index​

A plain first index of a git worktree reuses a sibling's index without worktree create. When tea-rags index-codebase (CLI) or index_codebase (MCP) targets a path that has no collection yet, TeaRAGs looks in the project registry for other working trees of the same repository — paths whose .git resolves to the same shared git directory. If one of them passes every check below, its footprint is cloned onto the new collection and the run continues as an ordinary incremental index: the working tree is diffed against the cloned file-hash snapshot, and only added and modified files are embedded.

Candidates are tried newest index first — the same order in which a new worktree borrows its embedding and tuning config from the registry. A refused candidate hands over to the next one. When none qualifies, the run is an ordinary first index.

When it applies​

Seeding is attempted only when all of these hold:

  • the path has no collection yet (a first index);
  • the run is not --force / forceReindex and not --force-enrichments;
  • the run does not pass custom extensions or ignorePatterns — the sibling holds whatever its own runs selected;
  • it was not turned off with --no-worktree-seed (CLI) or seedFromWorktree: false (MCP).

Checks on the sibling​

The first five compare what the sibling's index was built with against what this run would stamp. They are the same stamps drift detection compares, so a seeded collection reports no drift that a fresh index would not.

CheckComparedRefusal reason
Qdrant backendThe sibling's registered backend (embedded, or external URL) against the one this run talks toqdrant-backend
Embedding modelModel name; then the stored weights canary against this run's providerembedding-model
Payload keysKeys recorded in the sibling's stats cache against this build's payload signalspayload-schema
Language versionsPer-language code versions of every language the sibling contains, plus the shared * axislanguage-versions
Index settingsEnv settings that change indexed data: chunking, AST, hybrid, git windows, codegraph and its on/offindex-env
Completed indexThe collection exists, its last index completed, and it has a file-hash snapshotsource-not-indexed
Not busyNo index run or background enrichment holds the siblingsource-busy
CloneThe footprint clone succeededclone-failed

Runtime settings — endpoints, pool sizes, batch sizes, timeouts — do not take part: they change how a run executes, not what it writes.

A sibling whose registry entry carries no env stamp at all (an entry written before tea-rags recorded one) is refused with index-env: none of its index settings can be compared, so the run indexes from scratch instead. Any index run on that sibling records the stamp and makes it eligible again.

The sibling is claimed for the duration of the clone the same way an index run claims its own collection (in-process set, the <collection>.indexing.lock file, the Qdrant in-flight markers). A sibling that is being indexed, or whose background enrichment has not settled, is skipped rather than waited on, and no index run can start on it while its snapshot is taken.

What is copied and what is recomputed​

DataIn the seeded collection
Vectors and chunk payload, unchanged filesCopied verbatim from the sibling
Added and modified filesChunked and embedded by this run
Files deleted in this worktreeRemoved
Codegraph databaseCopied, then repaired for changed files by the incremental run like any incremental
Git signals (git.*)Rebuilt for every point from this worktree's history, as background enrichment
Stats cacheCopied, then recomputed when the run finishes
Quarantine listCopied; quarantined files are retried by the run
Language version stampsRecorded as a full index records them

Vectors, chunk payload and the codegraph are functions of file content, which the hash match proves identical. Git signals are not: they are read from the history reachable from the worktree's own HEAD and depend on the time of enrichment. The git rebuild runs after the index is searchable; the new collection stays locked until it finishes, so a second index run on it in that window is rejected as already in progress. With git enrichment disabled there is nothing to rebuild.

If the run over a fresh seed fails, the seeded collection is dropped, so the next attempt starts from a clean slate instead of an unstamped clone.

If the process dies instead — killed during the seeded run, or an MCP server restarted while the git rebuild was still going — the collection remembers what it still owes: the version stamps and the git rebuild. The next index run on it (an ordinary incremental one) finishes both, again with the git rebuild in the background. A git rebuild that fails is retried the same way.

Seeded collections are ordinary projects​

Automatic seeding writes no worktree provenance. The collection is registered by the run like any first index — under the alias given with --name, or unnamed — and is not listed by worktree list. worktree remove refuses it; remove it with tea-rags projects unregister --name <alias> --purge (or --path <worktree> --purge when it is unnamed) or clear_index.

The worktreeSeed report​

A first index reports what happened under worktreeSeed: in the index-codebase --json output, as a "Worktree seed" block in the human CLI status, and as a "Worktree seed:" block at the top of the index_codebase MCP response. The field is absent from incremental, forced and recompute runs.

Seeded:

"worktreeSeed": {
"status": "seeded",
"source": { "collectionName": "code_1a2b3c4d", "project": "my-project", "path": "/repo/main" },
"filesCopied": 1893,
"filesIndexed": 6,
"filesRemoved": 1,
"gitRefresh": "background",
"rejected": []
}
FieldMeaning
sourceThe sibling the collection was cloned from
filesCopiedFiles of the sibling's snapshot kept verbatim (its files minus modified, deleted, newly ignored)
filesIndexedFiles embedded by this run (added + modified)
filesRemovedFiles present in the sibling only (deleted or newly ignored here)
gitRefreshbackground when git signals are rebuilt after the run; not-applicable when git enrichment is off
rejectedNewer siblings refused before source was accepted, each with reason and detail

Not seeded:

"worktreeSeed": {
"status": "skipped",
"reason": "no-compatible-sibling",
"rejected": [
{
"collectionName": "code_1a2b3c4d",
"project": "my-project",
"path": "/repo/main",
"reason": "language-versions",
"detail": "typescript.chunking 2 → 3"
}
]
}

reason is one of disabled (opted out), restricted-run (custom extensions or ignore patterns), no-sibling (no other working tree of the repository is registered — the ordinary first index; the CLI and MCP text stay silent about it) and no-compatible-sibling (every candidate was refused; rejected says why).

Opting out​

tea-rags index-codebase --no-worktree-seed /path/to/worktree
{ "path": "/path/to/worktree", "seedFromWorktree": false }

Both force an ordinary first index; on any other run they have no effect.

Commands​

All operations live under the tea-rags worktree command group. Running tea-rags worktree with no subcommand is equivalent to tea-rags worktree list.

tea-rags worktree create​

tea-rags worktree create <name> [--from <alias>] [--path <dir>] [--branch <b>] [--no-git] [--json]

Clones the source index into a new worktree collection and registers it with provenance. By default it also creates the git worktree itself (git worktree add); the clone does not reindex — that is a separate, user-gated step.

FlagDefaultDescription
<name>— (required)Worktree name. Forms the alias <source>-worktree-<name>.
--fromcwd projectSource project alias to clone from.
--path./<name>Where the git worktree lives on disk.
--branch—Git branch to create in the worktree (git worktree add -b).
--no-gitgit is createdSkip git-worktree creation; attach the index to an existing dir.
--jsonfalseEmit the result as JSON instead of human text.
$ tea-rags worktree create feature-xyz --branch feature-xyz
Created worktree 'tea-rags-worktree-feature-xyz' -> code_a1b2c3d4 at ./feature-xyz
Next: index the live diff with tea-rags index-codebase --project tea-rags-worktree-feature-xyz

The Next: hint uses --project (not --name): create already registered the alias with worktree provenance, so the follow-up reindex must reference the existing entry rather than register a second, provenance-less one.

With --json:

{
"collectionName": "code_a1b2c3d4",
"alias": "tea-rags-worktree-feature-xyz",
"worktreePath": "./feature-xyz",
"nextStep": "tea-rags index-codebase --project tea-rags-worktree-feature-xyz"
}

On failure: worktree create failed: <reason> to stderr, exit 1 (the saga has already rolled back any partial clone).

tea-rags worktree list​

tea-rags worktree list [--json]

Lists only worktree-derived collections (entries with worktreeOf set) — real projects are excluded. Tab-separated columns: name, alias, <- source, chunk count.

$ tea-rags worktree list
feature-xyz tea-rags-worktree-feature-xyz <- code_proj 3832 chunks
# No worktree indexes. # when empty

--json emits the full entries (including worktreeOf, chunksCount).

tea-rags worktree remove​

tea-rags worktree remove <name> [--force] [--keep-git] [--json]

Tears down a worktree collection: removes the footprint artifacts (best-effort, reverse order; a live run's indexing lock is left in place), drops the registry entry, then removes the git worktree directory.

FlagDefaultDescription
<name>— (required)Worktree name (not the collection or alias).
--forcefalseRemove even with uncommitted changes in the git worktree.
--keep-gitgit dir is removedKeep the git worktree directory; only remove the index.
--jsonfalseEmit the result as JSON.
$ tea-rags worktree remove feature-xyz
Removed worktree 'feature-xyz'.
# 'feature-xyz' was not found. # when the name is not a worktree clone

The provenance guard means a name that resolves to a real project (no worktreeOf) is reported as "not found" rather than destroyed.

tea-rags worktree info​

tea-rags worktree info [--json]

Resolves the current directory to its registered collection and reports whether it is a worktree clone.

{ "isWorktree": true, "collectionName": "code_a1b2c3d4", "alias": "tea-rags-worktree-feature-xyz", "worktreeOf": "code_proj", "worktreeName": "feature-xyz", "chunksCount": 3832 }

When the directory is a real project (or unregistered), it returns { "isWorktree": false }.

Workflow​

# 1. Clone the index + create the git worktree
tea-rags worktree create feature-xyz --branch feature-xyz

# 2. Index the live diff (user-gated — re-embeds only branch changes)
tea-rags index-codebase --project tea-rags-worktree-feature-xyz

# 3. Search the worktree against its own clone — point any MCP search/explore
# tool at the worktree alias (the agent passes
# project: tea-rags-worktree-feature-xyz), resolving against the clone, not main.

# 4. Edit, commit — the auto-reindex hook keeps the clone fresh (see below)

# 5. Clean up when the branch is done
tea-rags worktree remove feature-xyz

Step 4's freshness is automatic when the TeaRAGs plugin is installed: the git-workflow auto-reindex hook detects commits inside the worktree and incrementally reindexes the clone, so search results track the live branch without manual reindex calls.