The lock file: vibe.lock
01vibe.lock is the file vibe writes and you commit: the exact versions the project got. This page explains every field so you can read a diff of it in a pull request and know what changed.
Where it lives and who writes it
02There is one vibe.lock per workspace, at the absolute root, beside the root manifest; members never have their own. vibe install and vibe update write it; vibe clean keeps it; nothing edits it by hand. It is the recorded decision; the dependency tree on disk is only its consequence and can always be rebuilt from it.
03 Nesting is hierarchical grouping, not independent resolution domains. The lockfile and unified resolution always live at the absolute root of the workspace tree. A nested[workspace]provides (a) the[workspace.versions]matryoshka (§2.6) and (b) logical grouping of members — never its own lockfile, never its own resolution pass.
04 Never touched —vibe.lock. The lock is the recorded resolution, not derived state: keeping it is what makesvibe clean install --offlinereproduce the exact world from the machine cache with zero network — the mvn analogy istarget/vs the dependency resolution, and the lock sits on the resolution side.
[meta]
| Field | Meaning |
|---|---|
generated_by, generated_at |
the vibe version and the time of the last write; informational |
schema_version |
the lock schema; an older vibe refuses a newer schema rather than misreading it |
solver |
the resolver that produced the graph |
root_dependencies |
the coordinates the manifests asked for directly; the baseline of the freshness check |
language_chain |
the resolved language preference and its fallbacks, so a re-install on another machine materialises the same files |
06 The freshness check itself adds novibe.lockfield: the lockfile is the baseline, and the check is acargo-style satisfiability test of the locked versions against the current[requires]— see §5.1.
07[meta].language_chain(§2.7.5) — shipped as one ordered field merging the preference and its fallback, not thelanguage+language_fallbackpair this section drafted.
08root_dependencies copies the manifest's [requires.packages], so the lock file is a self-contained snapshot that needs no manifest to read. Removing a root with vibe uninstall drops it from both files; removing a package that is only a transitive dependency is refused with an explanation.
09 Decision.vibe.lockgainsschema_version = 2and the following record shape per package:
10root_dependenciesis a mirror ofvibe.toml[requires].packages— the lockfile keeps the user's declared roots inline so it remains a self-contained snapshot of the solve state (nothing insidevibe.lockrequires readingvibe.tomlto interpret). The source of truth for what the user asked for is the manifest's[requires]section; the lockfile carries a copy plus the resolved transitive closure.vibe uninstallof a root drops the entry from both files;vibe uninstallof a pure transitive is rejected with an explanation.
[[package]]
| Field | Meaning |
|---|---|
group, name, version |
the coordinate that was resolved |
kind |
the package's kind, for placement and filters |
content_hash |
the fingerprint of the package's shippable tree; the identity; verified on every fetch |
registry |
the name of the registry that answered, from the manifest's list |
source_url, source_ref, resolved_commit |
where and at what git ref the bytes were fetched this time; informational, always the canonical address even when a mirror served |
source_kind |
registry, git, override or path: which resolution path produced the entry; for path the URL field holds the member's folder relative to the root |
dependencies |
the resolved dependencies of this package, as exact coordinates |
overridden |
true when an [[override]] supplied the package |
features, subskills |
the active features and subskills recorded for the package |
files_written |
the project files this package's install wrote outside its own folder, so that uninstall can remove exactly them |
via_redirect |
the address of the registry stub that delegated the package elsewhere, when one was followed; absent otherwise |
12 Decision. A package's identity is the tuple(kind, name, version, content_hash). Thecontent_hashis a digest over the deterministically-ordered concatenation of(rel_path_bytes || 0x00 || file_bytes || 0x00)for every file in the package directory, and the value names the recipe that produced it (PROP-044 §4.7):sha256-tree/1:<hex>is recipe 1, whose exclusion list, path normalisation and traversal order are carried as data informats/hash_recipes/1.toml; the baresha256:<hex>is recipe 0, the pre-recipe form, frozen verbatim in code — not configurable, because a frozen recipe that can be edited is not frozen — so that values written before recipes were named stay readable. Two hashes are comparable only at the same recipe; comparing across recipes answers a question nobody asked, and is never done silently. PROP-024 §2.2 re-scopes this to the package's shippable tree — its source, minus build output (.git/,.vibe/,target/,node_modules/,.vibeignoreglobs) — so a code-bearing package's identity is its source, not its build state; that exclusion lands with the code that implements it. The URL used to fetch the content is informational — recorded in the lockfile for debuggability, not for identity.
13source_kindis"registry"for the M1.13 default,"git"for git-source declarations,"override"for[[override]]-resolved (existingoverridden = trueis preserved as redundant marker for back-compat). Lockfile schema bumps to v3; v2 lockfiles read transparently and migrate to v3 on next install (everything that wasoverridden = truebecomessource_kind = "override"; everything else"registry").
14
The canonical URL is always recorded as the source_url in the lockfile when the fetch produces a new pin. Mirror URLs do not appear there.
Reading a diff
15A changed version with a changed content_hash is an update. A changed content_hash with the same version is impossible in a healthy world: vibe refuses it at fetch time, so if you see it in a diff someone edited the file. A changed source_url alone is a mirror or a host migration and means nothing for the project. A new entry with source_kind = "override" is a patch someone applied on purpose and should carry a reason in the commit message.
16 mirror-switching, host-migration, and vendoring never change the lockfile;
Edge cases and rules
17An unchanged manifest against an unchanged lock file makes vibe install skip the resolver entirely: the lock is the answer.
18 With the freshness check,vibe installbecomes lockfile-respecting: unchanged[requires]⇒ the locked versions are honoured verbatim.
19The lock file records no registry index and no mirror: reproducing it needs only the coordinates, the fingerprints and a source that can serve them.
20vibe why <coordinate> explains from the lock file and the manifests why a package is in the project, or what blocked it.
21The file is read strictly: a field vibe does not know is an error, not a warning, so a hand edit or a field from a newer vibe is caught at once.