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.
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-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.
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