Files LPM CLI creates
Which files LPM CLI writes in a project, which ones to commit, and which stay on your machine.
LPM CLI follows one naming rule in a project:
- Files and directories named
lpm.*orlpm-*at the project root are team files. Commit them. - The
.lpm/directory holds machine-local state. It keeps itself out of Git, so it needs no.gitignoreentry.
Data that belongs to you rather than to a project, such as your login and local env secrets, lives in the LPM home, outside the project.
my-app/
├── package.json commit
├── lpm.lock commit
├── lpm.json commit
├── lpm.toml commit
├── lpm-skills/ commit
├── lpm-changes/ commit
├── lpm-sources.json commit
├── lpm-agent-skills.json commit
├── patches/ commit
├── .env.example commit
└── .lpm/ local, ignores itselfEach file appears only after you or a command creates it.
Files to commit
| Path | Created by | Purpose |
|---|---|---|
package.json | You, lpm init, and commands that change dependencies or trust, such as lpm install, lpm approve-scripts, and lpm patch-commit | Dependencies, scripts, and the "lpm" block of shared package policy. |
lpm.lock | lpm install | The resolved dependency graph. A fresh clone installs from it, including a project with overrides or patches, and offline installs can use it too. See Lockfile. |
lpm.json | You, lpm init --npm, and lpm env, which adds the vault ID | Runtimes, tasks, services, env files and schema, publish targets, and the project's env ID. The ID is not a secret. See lpm.json. |
lpm.toml | You, and lpm install when you allow a suspicious package name at its prompt | Team CLI defaults and reviewable policy exceptions, such as policy.typosquat.allow entries. See lpm.toml. |
patches/ | lpm patch-commit | Patch files that package.json > lpm.patchedDependencies names. |
lpm-skills/ | You | Agent skills your package publishes. lpm publish ships them at lpm-skills/ in the tarball. lpm skills validate checks them with the rules LPM.dev Registry applies at upload. |
lpm-changes/ | You | Release change files at the workspace root. lpm release apply removes the entries it applied after it writes the new versions. |
lpm-sources.json | lpm add | What lpm add delivered: each source package and version, the files it created or replaced with their digests, the directories it created, and the dependencies it added. lpm remove reads it on any checkout. |
Files that lpm add copied | lpm add | Source code delivered into your project. It is your code now. |
lpm-agent-skills.json | lpm skills add --project | The project's managed agent skills and the revision of each. lpm install restores what a checkout lacks. See Skills. |
.env.example | lpm env example | Variable names, descriptions, and defaults from envSchema, without values. LPM CLI creates it with your usual file permissions. |
Packages/LPMDependencies/ and the .xcodeproj changes | lpm install in an Xcode project | The wrapper package that exports your Swift dependencies and the project's link to it. Commit them with the shared Package.resolved files. See Swift. |
Agent folders such as .claude/skills/<name> link to skill content in .lpm/managed-skills/, or hold a copy of it. lpm install restores the content and agent folders that a checkout lacks. It warns about a skill that it can't restore and points to lpm skills update. Where a repository commits such a link and Git checks it out as a plain file, as on Windows without link support, lpm install replaces that file.
Records from earlier releases
Earlier releases kept some of these records in .lpm/, where Git now never sees them:
| Earlier location | Now | What LPM CLI does |
|---|---|---|
.lpm/added-sources.json | lpm-sources.json | Reads it, and replaces it with lpm-sources.json the next time it records a change. Packages that a teammate's committed lpm-sources.json lacks join it. |
.lpm/managed-skills/skills.lock.json | lpm-agent-skills.json | Reads it, and replaces it with lpm-agent-skills.json the next time it records a change. Skills that a teammate's committed lpm-agent-skills.json lacks join it. |
.lpm/changes/ | lpm-changes/ | lpm release plan and lpm release apply stop until you move the files. |
Authored skills in .lpm/skills/ | lpm-skills/ | lpm skills validate and lpm publish fail until you move them. |
What stays local: .lpm/
Every command that writes project state keeps a .lpm/.gitignore file that holds *. Git then ignores everything in .lpm/, wherever the project sits in a repository. LPM CLI does not edit your project's .gitignore for it. A deploy output is the exception: its .lpm/ holds the output's dependencies, which its .gitignore lets Git commit, along with the .gitignore itself.
LPM CLI keeps .lpm/ owner-only on Unix and refuses a symbolic link in its place. It creates the directory that way, removes group and other access from one that has it, such as one an earlier version created, and on macOS removes the access list entries that the project directory passes on. Another user's .lpm/ stays as it is. If .lpm/.gitignore stops ignoring everything, for example because a negation follows *, LPM CLI replaces it. It leaves a read-only or linked file alone.
In .lpm/ | Contents | Why it stays local |
|---|---|---|
install-hash, build-state.json, trust-snapshot.json, overrides-state.json, patch-state.json | Install checkpoints, blocked lifecycle scripts, and the trust and patch state of the last install | Installs rebuild them. A committed copy can hide the warnings that they exist to raise. |
wrappers/, hoisted/ | Package directories that some node_modules layouts keep here | Installs recreate them. |
skills/<owner.package>/ | Skills of installed @lpm.dev packages | lpm install restores them. |
managed-skills/ | Content of the project's managed agent skills | lpm install restores it from lpm-agent-skills.json. |
added-source-backups/ | Copies of the files that lpm add replaced | They belong to the machine that ran lpm add. On another checkout, lpm remove keeps such a file and tells you to restore the original from version control. |
certs/ | The project's development certificate and its private key, key.pem | A private key never belongs in a repository. See lpm cert. |
inspector.db and its -wal, -shm, and -lock files | Tunnel captures, including raw request headers and bodies | Captures can hold tokens, cookies, and personal data. |
env-sync.json | This checkout's env sync state: cloud versions, account bindings, and revision floors | Each checkout keeps its own. A committed copy would bind teammates' clones to your account. |
migrate-backup/ | Originals of the files that lpm migrate changed | lpm migrate --rollback uses them. They can include an .npmrc with a token. |
xcode-backups/ | project.pbxproj as it was before the last Xcode project edit by LPM CLI | For recovery on this machine. |
release-apply/, rollback and restore staging directories | Recovery state of interrupted releases, workspace installs, and task cache restores | It only applies to this machine. |
graph.html | The view that lpm graph --format html writes | Regenerated on demand. |
| Lock files | Locks that serialize installs, publishes, migrations, and lpm.json updates | They coordinate processes on this machine. |
If Git already tracks files in .lpm/
Files committed before .lpm/ ignored itself stay tracked, because ignore rules don't untrack files. lpm doctor --all reports them as project_state_tracked, names up to three, and gives a command that untracks them from any directory. Git can track the directory under a spelling that the file system takes for .lpm/, such as .LPM/ on a case-insensitive file system, or the name with a zero-width mark in it on HFS+ or in a Linux casefold directory; the check counts those files, and the command uses Git's spelling. A repository of .lpm/'s own, such as a clone or a submodule there, is checked too, with its own command. In a deploy output, whose .lpm/ commits its dependencies, the check leaves out .lpm/.gitignore, .lpm/store/, and .lpm/deploy-workspace/, and the command names only the other files; run that command instead of the one below. Elsewhere, untrack them once and commit:
git rm -r --cached .lpm
git commit -m "Stop tracking .lpm"The files stay on disk, and .lpm/ keeps them out of later commits. Until you untrack them, lpm cert refuses to write the project's private key into .lpm/certs/ while Git tracks a file there, in the project's repository or in one at .lpm/ or .lpm/certs/, or while Git can't tell whether it does, as when Git can't be run in a project inside a repository. Env sync commands refuse a .lpm/env-sync.json that Git tracks, and warn when Git can't tell; see env sync state.
.gitignore entries from earlier releases
Earlier releases appended blocks such as this one to the project's .gitignore:
# LPM wrapper tree (auto-generated)
.lpm/wrappers/lpm install removes the wrapper, hoisted-state, and Agent Skills blocks when they appear exactly as LPM CLI wrote them. It deletes .gitignore when nothing else is left in it. A .lpm/ rule that you wrote yourself stays; it is harmless but no longer needed.
Lifecycle scripts and publishing
Sandboxed dependency lifecycle scripts can read the package directories in .lpm/, wrappers/ and hoisted/nested/, and write only their own package's directory there. With the default readProject: "narrow", those are also the only parts of .lpm/ that they can read. package.json > lpm.scripts.sandboxWriteDirs rejects entries inside .lpm or above the project, on Linux scripts don't run when a directory they can write, or a mount below one, shows .lpm, the LPM home or the repository's git directory under another path, and a project inside ~/.cache, ~/.npm, or ~/.node-gyp, which scripts can write, can't run dependency scripts in the sandbox. See the lpm.scripts block.
lpm publish never packs .lpm/ at any depth, even when files or an entrypoint names it; an entrypoint that reaches it by another spelling, such as the NTFS short name LPM~1, stops the publish. It also leaves out the root lpm.lock, lpm.lockb, lpm-changes/, lpm-sources.json, and lpm-agent-skills.json. A published lpm.json keeps its other fields but drops vault and vaultSync. See lpm publish.
Secret files in a project
Some commands write real secrets into project files. Before a secret reaches disk, LPM CLI makes sure that Git ignores the file.
| Command | File | Ignore rule goes to | When the file can't be kept out of Git |
|---|---|---|---|
lpm env export <file> | The exported values | .gitignore | The command stops and writes nothing. |
lpm env export --ci <file> | The resolved CI values | .git/info/exclude | The command stops and writes nothing. |
lpm env pull --oidc --output=<file> | The pulled values | .git/info/exclude | The command stops and writes nothing. |
lpm setup local | .npmrc with a project token | .gitignore | The command stops before it changes the token. |
lpm setup ci npmrc | .npmrc with a CI token | .git/info/exclude | The command stops before it changes the token. |
lpm dev | .env, copied from .env.example when .env is missing | .gitignore | A warning; the copy is written. When Git tracks .env, a warning and no copy. |
lpm env import <file>, lpm env init | The env files they read | .gitignore | A warning; the import is done. |
For a file inside the project, or elsewhere in a Git work tree, LPM CLI does this:
- It refuses a destination that Git tracks, including one that Git tracks under a spelling the file system takes for the same file, such as
.ENVfor.envon a case-insensitive file system,ſforson APFS, the name with a zero-width mark in it on HFS+ or in a Linux casefold directory, or on Windows an NTFS short name such asENV~1or a name that ends in a dot or a space, such as.env.. It asks every repository whose index can list the file: the one the project is in, one cloned or added as a submodule in a directory on the way to the file, and one around either of those, whose index still lists the files it had before a repository was made inside it. A link at the destination counts as the link, which the write replaces, not as the file it leads to. It names a command that untracks the file from any directory, one for each repository that tracks it, such asgit -C <directory> rm --cached -- .ENV.importandinitonly read their files, so a tracked source file is a warning, and they keep the file out of Git where it is once links are followed. - If Git doesn't already ignore the file, it appends an anchored rule for that exact path, such as
/.env.production, and checks that Git now ignores the file. When the path names a directory by another spelling, such asINFRA/forinfra/, a rule for the name the directory is stored under joins it. The rule goes to the project's.gitignore, or for a file in another work tree, such as a repository cloned inside the project or a monorepo's root outside it, to the.gitignoreat the root of that work tree. Commands for CI use the repository's.git/info/exclude, which leaves the work tree clean. Another repository whose index lists files in that directory, comparing names as itscore.ignorecasesetting says, looks inside it, so LPM CLI checks that it ignores the file too. That repository reads the work tree's.gitignoreon its way in, but not the work tree's.git/info/exclude, so commands for CI add the rule to its own.git/info/exclude. A repository that records the directory as a submodule never looks inside it, and needs no rule. - It writes the file atomically with owner-only permissions:
0600on Unix, without access list entries that the directory would pass on on macOS, and a protected access list on Windows. In the project, it refuses a parent directory that is a link or doesn't exist, under a nested repository too. Outside the project, it follows links and keeps the file they lead to out of Git.
Outside a Git repository, LPM CLI still adds the .gitignore rule, so the file stays ignored after you create a repository. A rule that it can't write there is a warning. When Git can't be run at all, LPM CLI writes the file with a warning that it couldn't confirm that Git ignores it; commands that use .gitignore still add the rule, under a repository cloned inside the project to the .gitignore beside its .git. Commands that only warn when Git can't answer, such as lpm dev, add the rule the same way. lpm setup local and lpm setup ci npmrc stop instead when the project is inside a repository, as they can't tell whether Git tracks the .npmrc that will hold a token. A file in no Git work tree, outside the project, is written owner-only without changes to any ignore file. When Git can't tell whether a work tree holds such a file, for example because of a .git file that points nowhere, the commands that stop above stop, and the others write it with a warning. JSON output lists warnings in warnings.
Which directory is the project
Commands that keep project state, such as lpm env, lpm dev, lpm tunnel, and lpm cert, act on the nearest directory at or above the current one that has a package.json or an lpm.json. The search stops before your home directory, also where HOME names it through a link. Package commands such as lpm add, lpm remove, and lpm publish use the nearest package.json, stopping there too.
LPM CLI prints the directory that it chose when it isn't the current one. File paths that you type, such as lpm env export local.env, lpm env generate --out-dir src/env, lpm add <package> --path widgets, lpm publish --provenance-file bundle.sigstore, or lpm skills add ./team-skills, stay relative to the directory where you ran the command. lpm release publish --provenance-file is the exception: each released package has its own bundle, so a relative path names a file in each package's directory. Paths in package.json and lpm.json are relative to the project.
Without a project around it, a command uses the current directory. Commands that write project state refuse a directory whose .lpm would be the LPM home, such as your home directory. A project that is your home directory, as HOME=/app with the app in /app makes it, needs LPM_HOME set to a directory outside the project. Account actions that use no project, such as lpm env pair, unpair, ls-remote, and rotate-sharing-key, run in any directory.
The LPM home
Data that isn't part of any project lives in the LPM home: ~/.lpm by default, %USERPROFILE%\.lpm on Windows, or the directory that LPM_HOME names. Keep it out of repositories.
Every command gives the LPM home a .gitignore that ignores everything in it. When a repository holds the home, as LPM_HOME="$PWD/.lpm-ci" does in CI, the command does so before it writes anything there, creating the home with that .gitignore if it doesn't exist yet. A .gitignore already there stays as it is, and a repository's root gets none, as it would hide the whole repository. Inside a repository, a command stops when Git would commit what LPM CLI adds to the home: give the home a .gitignore that ignores everything in it, or point LPM_HOME at a directory of its own. A home that is a repository's root, without a .gitignore of its own that ignores everything as described next, can only be moved. lpm logout, lpm cert status, and lpm cert uninstall still run in such a home, with the same message as a warning, so you can remove the login and certificate trust it already holds. A .gitignore that ignores everything and then takes back only config.toml and the .gitignore itself, as a dotfiles repository that commits them does, is enough; one that takes back anything else isn't, whatever a repository's own exclude file says. This holds wherever a linked LPM_HOME leads. lpm publish and lpm deploy leave the LPM home out of a package that holds it, bundled dependencies included. The package store and the task cache keep their directories to their owner (0700), on macOS without the access list entries a directory above would pass on, and a task cache entry that another account could write isn't restored. Give the LPM home a directory of its own.
| In the LPM home | Contents |
|---|---|
config.toml | User defaults. |
store/, cache/ | The package store and caches. |
global/, bin/ | Globally installed packages and their commands. |
.credentials and its key files | Login state, when the OS credential store can't hold it. See Authentication. |
vaults/, .vault-fallback-key | Local env secrets on Linux and Windows. See Local secrets. |
certs/ | The local root certificate authority, including its private key. |
security/ | Security approvals. |
cert-projects.json, known-projects.json, ports.toml | Records keyed by project path: projects with development certificates, projects whose packages the store keeps, and per-project port choices. |
An LPM home other than ~/.lpm keeps its own entries in the OS credential store, so it has its own login, local env secrets, and approvals. Switching LPM_HOME doesn't carry them over. lpm doctor warns with lpm_home_default_state_unused when ~/.lpm holds login or env files that the selected home doesn't use.
Project env values can't set LPM_HOME for the scripts that LPM CLI starts: it drops the variable from .env files and stored env values. lpm self-update keeps its state in .lpm in your OS account's home directory, whatever LPM_HOME names.
See also
- Project setup — what
package.json,lpm.json, andlpm.tomlown lpm doctor— project state and storage checkslpm env— env files, exports, and sync state- Environment variables —
LPM_HOME