# lpm store (/docs/infra/store)



```bash
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](/docs/packages/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`](/docs/packages/cache). `lpm store` covers integrity, the store path, and the blunt full wipe.

## Examples [#examples]

```bash
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 [#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 [#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 probe `WouldBlock`s, 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`, and `lpm cache prune` (dry-run) always acquire the shared lock for the duration of the command. `lpm audit` and `lpm query` acquire 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 — two `lpm install` invocations in different shells share without contention.
* **Exclusive (single-writer):** `lpm store clean` and `lpm 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 [#verify]

```bash
lpm store verify          # fast pass
lpm store verify --deep   # also parse package.json + lockfile cross-check
lpm store verify --fix    # regenerate stale behavioral-analysis caches
```

The 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.json`** and confirms the on-disk `name` and `version` match the directory's coordinates.
* **Cross-checks the integrity hash** against `lpm.lock` in the current directory. Mismatches surface as `integrity mismatch` errors — 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. If `lpm.lock` exists 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) [#clean-full-wipe]

```bash
lpm store clean
```

Removes `~/.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`](/docs/packages/cache), which is reference-aware and won't evict entries a project still needs.

## JSON output [#json-output]

```bash
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 subdirectories
```

`verify` 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 [#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](/docs/commands#global-flags).

## See also [#see-also]

* [`lpm cache prune`](/docs/packages/cache) — reference-aware orphan removal for the global store
* [Content-addressable store](/docs/packages/content-addressable-store) — design overview
* [`lpm doctor`](/docs/infra/doctor) — surfaces store-integrity issues alongside other checks
