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 spec: Nesting is hierarchical grouping, not independent…
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 spec: Never touched — vibe.lock
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 spec: The freshness check itself adds no…
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 spec: [meta].language_chain (§2.7.5) — shipped as one…
[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 spec: Decision: vibe.lock gains schema_version = 2…
Decision.vibe.lockgainsschema_version = 2and the following record shape per package:
10 spec: root_dependencies is a mirror of vibe.toml…
root_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 spec: Decision: A package's identity…
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.
13 spec: source_kind is "registry" for the M1.13…
source_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 spec: The canonical URL is always recorded…
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 quote from the specification
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 spec: With the freshness check, vibe install…
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.