lpm store
Verify integrity, print the path, or wipe the global content-addressable package store.
lpm store <action>Maintenance commands for the global content-addressable store at ~/.lpm/store/. Every lpm install deduplicates packages into this store and then links them into project node_modules trees — so the store is shared across every project on your machine.
The v2 store also contains native lifecycle-build artifacts at
~/.lpm/store/v2/builds/<build-key>/. Strict macOS and Linux rebuilds can
restore these package trees across reinstalls when every keyed input matches.
That includes the absolute checkout path and complete lifecycle environment,
so a differently located project or a CI run with changing values such as
GITHUB_RUN_ID receives a different key unless the caller normalizes those
inputs. Per-key advisory locks under ~/.lpm/store/v2/build-locks/ prevent
concurrent invocations from performing the same native build twice, while
~/.lpm/store/v2/build-entry-locks/ serializes mutations of each shared graph
entry across different build keys. See
lpm rebuild — Native build cache.
The expensive host-toolchain portion of those keys has a validated snapshot
under ~/.lpm/cache/metadata/native-toolchains/v1/. It is metadata rather than
a build artifact: lpm cache clean metadata removes it, while lpm store clean removes native build artifacts. LPM CLI recomputes the complete host
fingerprint when the snapshot is missing, changed, invalid, or older than one
hour.
For reachability-aware orphan cleanup, use lpm cache prune. lpm store covers integrity, the store path, and the blunt full wipe.
Examples
lpm store path # print the store root
lpm store verify # check integrity (fast)
lpm store verify --deep # also parse package.json + check name/version match
lpm store verify --fix # auto-fix what it can (e.g., refresh stale caches)
lpm store clean # wipe the entire store (rare)Subcommands
| Subcommand | Effect |
|---|---|
path | Print the store root path |
verify | Integrity-check entries against their declared SRI |
clean | Blunt wipe — removes everything under the store root |
Locking model
Store operations coordinate through a writer-preference reader/writer protocol built on three advisory lock files:
- Data lock at
~/.lpm/store/.gc.lock— the access lock. Shared by readers in the body, exclusive by writers in the body. - Writer-intent gate at
~/.lpm/store/.gc.lock.writer-intent— derived automatically. Held exclusive by the writer that's currently running, so new readers admitted at the gate are blocked until the writer finishes. - Writer-queue baton at
~/.lpm/store/.gc.lock.writer-queue— derived automatically. Held shared by every queued writer (including the one in the body). Readers probe it with a non-blocking exclusive try before attempting the gate; if the probeWouldBlocks, at least one writer is queued and the reader backs off. This closes the multi-writer leapfrog hole the gate alone leaves open — between writer W1 releasing the gate and writer W2 polling for it, the kernel could otherwise let queued readers grab gate-shared first and starve W2.
Surface:
- Shared (multi-reader):
lpm install,lpm patch,lpm rebuild,lpm approve-scripts,lpm store verify, andlpm cache prune(dry-run) always acquire the shared lock for the duration of the command.lpm auditandlpm queryacquire it only when the project has LPM CLI store-backed packages, and they narrow the lock to just the store-touching slice (audit's behavioral-analysis pass; query's inventory load + lifecycle/build-state loop) — discovery, registry calls, and OSV calls run unlocked. Multiple readers run concurrently — twolpm installinvocations in different shells share without contention. - Exclusive (single-writer):
lpm store cleanandlpm cache prune --apply. They take the queue baton shared (signaling "writer queued"), then the gate exclusive (blocking new reader admissions), then wait for in-flight readers to release the data lock.
Native build-cache work adds two narrower locks beneath the shared store lock.
build-entry-locks/<graph-key>.lock is acquired first and serializes every
restore, rematerialization, lifecycle command, marker write, and publication
that mutates one shared link-package tree. Different graph entries can still
build concurrently. Inside that critical section,
build-locks/<build-key>.lock deduplicates identical artifacts; a waiter checks
the artifact again after acquiring the lock before deciding whether to execute.
Writer preference. Once a writer is queued, no new reader is admitted until every queued writer has run. The protocol holds even with multiple writers in line — W2 stays at the head of the queue across W1's release boundary because W2's queue-shared keeps the readers' probes blocked. Bias is intentional: writes are rare; install/query is common.
If contention lasts longer than one second, the blocked command emits a single Waiting for another lpm store operation to finish... line on stderr — no PID claim (the lock primitive doesn't expose owner introspection), but enough to confirm you aren't stuck. The hint fires once per acquisition, not on every poll iteration.
Locks release on normal exit and on panic — there's no stale-lock recovery to run by hand.
Verify
lpm store verify # fast pass
lpm store verify --deep # also parse package.json + lockfile cross-check
lpm store verify --fix # regenerate stale behavioral-analysis cachesThe fast pass walks every store entry and confirms three things: the directory exists, package.json is present, and the directory is non-empty. For v2 entries, it also checks that the link sidecar's source hash maps to the recorded object path and that the referenced object is present. Mismatches surface as corrupted entries with a remediation hint.
--deep extends the pass with two extra checks per entry:
- Parses
package.jsonand confirms the on-disknameandversionmatch the directory's coordinates. - Cross-checks the integrity hash against
lpm.lockin the current directory. Mismatches surface asintegrity mismatcherrors — they catch a re-published version with different bytes, accidental edits, or partial extraction. Run from a project root for this to do anything; without a lockfile, the cross-check is a no-op. Iflpm.lockexists but cannot be read or parsed, deep verification exits non-zero instead of silently skipping the cross-check.
--fix writes one specific thing: missing or stale .lpm-security.json behavioral-analysis caches alongside each package. It implies the deep/cache-analysis path even when --deep is omitted. If a cache cannot be written, the command exits non-zero and the failed .lpm-security.json write appears in issues[]. It does not repair integrity mismatches, restore missing directories, or fix invalid package.json — those are reported as corrupted regardless. Use lpm store clean for the blunt repair.
The output distinguishes entries (one per directory on disk; may double-count during a v1↔v2 migration or under cross-project graph-key splits where the same name@version legitimately lives at multiple paths) from unique coordinates (deduped on name@version). Both counts appear in human + JSON output so an in-flight migration doesn't look like an inflated package count.
Clean (full wipe)
lpm store cleanRemoves ~/.lpm/store/v1/ and ~/.lpm/store/v2/ in their entirety — every extracted package, every link entry, every cached integrity sidecar. The outer ~/.lpm/store/ directory plus the .gc.lock* control files stay so the next operation doesn't have to reinitialize them. The next lpm install rebuilds whatever it needs.
This is the "I want a from-scratch state" escape hatch. For normal maintenance use lpm cache prune, which is reference-aware and won't evict entries a project still needs.
JSON output
lpm store path --json # { "success": true, "path": "/Users/you/.lpm/store" }
lpm store verify --json # entry counts, unique coords, corrupted list
lpm store clean --json # bytes freed across v1 + v2 subdirectoriesverify envelope:
| Field | Meaning |
|---|---|
success | false when any integrity/sidecar/lockfile/cache-write issue prevents a clean verify |
check_kind | presence for the fast pass, lockfile_marker_consistency for --deep or --fix |
bytes_integrity_recomputed | Always false today — verify checks markers/metadata, not a Merkle hash of extracted bytes |
entries_verified | Number of store entries walked (one per directory on disk) |
verified | Legacy alias of entries_verified for older consumers |
unique_coords | Deduped count over name@version — different from entries_verified during v1↔v2 migration |
duplicated_entries | entries_verified − unique_coords |
corrupted | Number of entries with at least one failed check |
issues[] | Per-entry human-readable issue strings (e.g., "sharp@0.34.4 — integrity mismatch: stored 'sha512-aaa...' != lockfile 'sha512-bbb...'") |
securityMismatches / securityReanalyzed | Present on deep/fix runs; cache mismatches found vs. caches successfully refreshed |
clean envelope:
| Field | Meaning |
|---|---|
removed_bytes / removed | Total bytes / formatted total across v1 + v2 |
v1_removed_bytes / v2_removed_bytes | Per-subdirectory breakdown |
v1_path / v2_path | Absolute paths that were removed |
path | Legacy alias of v1_path retained for pre-v2 consumers — prefer v1_path / v2_path |
Flags
| Flag | Applies to | Effect |
|---|---|---|
--deep | verify | Also parse package.json, validate name/version, and cross-check lockfile integrity markers |
--fix | verify | Refresh stale/missing security caches; implies the deep/cache-analysis path |
Plus the global flags.
See also
lpm cache prune— reference-aware orphan removal for the global store- Content-addressable store — design overview
lpm doctor— surfaces store-integrity issues alongside other checks