Task runner
Parallel, cached, watchable, workspace-aware execution for package.json scripts.
The task runner powers lpm run. It runs package scripts and commands from lpm.json > tasks in dependency order.
Optional flags enable parallel tasks and file watching. Each task can also enable caching.
lpm dev uses separate service orchestration for development servers, readiness checks, and restarts.
Quickstart
Define tasks in lpm.json, then run the final task:
{
"tasks": {
"check": { "command": "tsc --noEmit" },
"build": {
"command": "tsup",
"dependsOn": ["check"]
},
"verify": { "dependsOn": ["check", "build"] }
}
}lpm run verify
lpm run verify --parallel
lpm run verify --json --no-cacheThe runner completes prerequisites before dependent tasks. It runs each reachable task once per invocation.
A task with only dependsOn groups its prerequisites without a separate command.
A task's command overrides the matching package.json script. Without command, the runner uses that script.
Package scripts run their pre<name> and post<name> hooks. Explicit task commands do not use package hooks.
Forwarded arguments also reach prerequisite commands, so use flags that those commands accept.
For all fields, see task configuration.
Dependencies
| Form | Meaning |
|---|---|
"check" | Run a prerequisite in the same project. |
"^build" | Run a prerequisite in upstream workspace members. Requires workspace selection. |
The complete task graph must be valid before any task starts.
Missing requested tasks, missing prerequisites, cycles, and malformed dependencies such as ^ or ^^build stop execution.
This preflight also applies to parallel and watch modes.
For workspace prerequisites, select workspace members explicitly:
{
"tasks": {
"build": { "dependsOn": ["^build"] }
}
}lpm run build --filter app
lpm run build --allThe runner expands upstream tasks through the complete workspace dependency chain.
Each required member must define the requested task. A filtered application still runs its upstream prerequisites.
Without workspace selection, a reachable ^task dependency produces an error instead of skipping that prerequisite.
Caching
Caching is opt-in per task:
{
"tasks": {
"build": {
"command": "tsup",
"cache": true,
"outputs": ["dist/**"]
}
}
}cache: true requires outputs to know what to cache. Without outputs, the
runner disables caching because it has nothing to record.
All inputs and outputs values must be valid, project-relative globs. LPM CLI
rejects invalid syntax, absolute paths, and parent traversal before it starts
the task. Files in the project's .lpm/ directory are never collected as
outputs, even when a pattern matches them.
Omitted or empty inputs uses default source and configuration patterns.
These include src, lib, app, pages, components, scripts, bin, and common root JavaScript and TypeScript files.
The defaults also include package and task configuration, TypeScript configuration, and common tool configuration filenames.
They do not include test, tests, public, or assets directories. Declare those paths when they affect the result.
* matches within one path component. ** spans directories. Braces are literal characters, so use separate patterns for alternatives.
The same patterns control watch inputs and output exclusions. Use dist/** to exclude a generated tree and its root directory from watch events.
Cache key
The cache key is a SHA-256 hash of:
- The
commandstring - Each forwarded script argument, with length boundaries
- The per-file SHA-256 of every file matching
inputsglobs - The cache identities of transitive same-project task dependencies
- The declared outputs of direct same-project task dependencies
- The complete
lpm.jsonfile, including dependency edges and output globs - The inherited environment after credential and runtime-hook removal, or the variables selected by
cacheEnv - All environment values loaded from project files, secrets, and schema defaults
- Managed Node and Bun versions, plus fingerprints of the selected executables
- The complete
package.jsoncontract, including scripts and resolution fields - Supported lockfiles in the project root
- The workspace-root configuration and lockfiles during workspace task runs
- The task identity of each reachable
^taskdependency
A changed input produces a different cache key. A matching key permits reuse after the cached result passes validation.
Use cacheEnv to select inherited variables that affect a task:
{
"tasks": {
"build": {
"cache": true,
"cacheEnv": ["NODE_ENV", "BUILD_TARGET"],
"outputs": ["dist/**"]
}
},
"remoteCache": { "enabled": true }
}Excluded inherited values, such as CI or GITHUB_RUN_ID, do not directly affect the key.
Runtime selection and project paths still affect it.
If your build uses CI to change behavior, include CI. Include every inherited variable that can affect the result.
Names are exact and case-sensitive. Wildcards are not supported.
Without cacheEnv, inherited values that survive credential and runtime-hook filtering remain cache inputs.
An empty list excludes those inherited values. CLI-provided script context, including absolute project paths, still affects the key.
Project-loaded values always remain cache inputs. cacheEnv changes neither the task environment nor remote-upload secret checks.
Executable paths and file metadata also affect runtime identity. Matching source and cacheEnv do not guarantee cache reuse across checkout paths or machines.
An ordinary prerequisite without a usable cache identity disables caching for its dependents.
For example, an uncached check prerequisite prevents a dependent build task from reusing a cached result.
Use caching only for tasks with reproducible declared outputs.
A local dependency identity includes its cache key and each reachable dependency identity. LPM CLI also validates each declared dependency output before a cache hit or cache publication. If an output changes, LPM CLI does not use or publish the downstream cache entry.
LPM CLI canonicalizes package.json before it hashes the manifest. Changes to
scripts, module settings, dependencies, peer metadata, overrides, resolutions,
catalogs, engine and platform selectors, and LPM CLI controls invalidate the cache.
LPM CLI always hashes lpm.lock. If the text lockfile is absent, LPM CLI hashes
lpm.lockb instead. When these ecosystem lockfiles exist, LPM CLI also hashes
them:
package-lock.jsonnpm-shrinkwrap.jsonyarn.lockpnpm-lock.yamlbun.lockbun.lockbdeno.lock
For workspace task runs, LPM CLI also hashes the root package.json, lpm.json,
and pnpm-workspace.yaml files. The root lockfiles use the same rules as the
member lockfiles.
Each ^task identity includes its command, arguments, environment, runtime,
inputs, configuration, manifest, lockfiles, and upstream task identities. An
upstream identity change invalidates the cache of each reachable downstream
task. If LPM CLI cannot calculate an upstream identity safely, it disables caching
for the dependent workspace member.
Cache layout
With LPM_HOME set, the cache is under $LPM_HOME/cache/tasks/.
~/.lpm/cache/tasks/
{sha256-hex-key}/
meta.json ← timing, command, key, output count
stdout.log ← captured stdout for replay
stderr.log ← captured stderr for replay
outputs.tar.gz ← archived output filesOn hit:
- Validate the metadata, logs, archive paths, file types, sizes, and output count.
- Stage all outputs in a temporary directory inside the project's
.lpm/, which Git ignores, without changing project outputs. - Remove declared output files that are absent from the cached result.
- Replace project outputs in one rollback-protected transaction.
- Preserve each archived file's mode and modification time.
- Replay
stdout.logandstderr.logto the current process. - Exit successfully.
LPM CLI treats an incomplete or corrupt local entry as a cache miss. It runs the task and replaces the bad entry. Concurrent writers for one key publish one complete entry.
LPM CLI writes a recovery journal before it replaces an output. If a process stops
during restore, the next restore in the project rolls back the incomplete operation
first. This also recovers .lpm-cache-restore-* directories that earlier LPM CLI
releases staged at the project root. If an immediate rollback fails, LPM CLI keeps
the recovery files and reports their path.
The restore rejects output paths that use an existing symlink or junction. The cache cannot write through that link to a location outside the project. When LPM CLI creates an archive, it also rejects symlink and junction outputs. This rule prevents a restored output from changing its file type.
The replay shows the same logs as a real lpm run build. The runner's
(cached) status tells you why the result was immediate.
Bypass
lpm run build --no-cache # bypass for one invocationTo disable task caching persistently, remove cache: true from its configuration.
Remote cache
Remote caching shares task results across machines through the LPM.dev Registry. Enable it with remoteCache.enabled or LPM_REMOTE_CACHE=1. Each cached task requires both cache: true and declared outputs.
LPM CLI checks the local cache first, then the remote cache. On a miss, it runs the task and caches successful results.
Artifacts contain declared outputs, captured logs, and task metadata. LPM CLI validates downloads before restore and applies the current project's output globs.
See Remote task cache for setup, organization tokens, CI recipes, signing, upload policy, and storage limits.
Manage
lpm cache path tasks # print cache root
lpm cache clean tasks # drop the task cache
lpm cache status --json # local usage + hosted cache statusSee lpm cache.
Parallelism and failures
lpm run lint test check --parallel
lpm run lint test check --parallel --stream --no-bail
lpm run build --all --workspace-concurrency 2--parallel runs independent tasks in host-sized batches within each dependency level.
The next level starts after the current level finishes. Output is buffered per task unless you pass --stream for prefixed live output.
Without --no-bail, a failed task stops later batches. Already-started tasks finish before the CLI exits, including after worker preparation errors.
With --no-bail, independent tasks continue. A failed prerequisite still blocks its dependents.
Workspace concurrency limits active members. It is separate from parallel tasks within a member.
The limit uses the CLI flag, then lpm.toml > [workspace].concurrency, then ~/.lpm/config.toml > workspace-concurrency, then host parallelism.
Workspace selection
lpm run build --all
lpm run test --filter web --filter api
lpm run test --filter './apps/*'
lpm run test --filter-prod ...shared
lpm run test --filter '!web-tests'
lpm run test --filter './apps/*' --fail-if-no-match--all selects every member and conflicts with filters and --affected.
Filters can combine with --affected. --filter-prod uses production dependency edges for dependency expansion.
--fail-if-no-match makes an empty selection fail, which helps detect mistakes in CI.
See workspace filters for names, paths, Git references, and dependency expansion. lpm test, lpm bench, and the built-in tools have their own workspace execution rules. For forwarded runner flags, see arguments and watch mode.
--affected
lpm run test --affected # default base = main
lpm run test --affected --base develop
lpm run test --affected --changed-files-ignore-pattern '**/README.md'
lpm run test --affected --test-pattern '**/*.test.js'Affected selection compares tracked working-tree files directly with the base ref, which defaults to main.
It includes staged and unstaged tracked changes. Untracked files are excluded.
It does not use a merge-base comparison.
Changed files select their owning workspace members. Changes outside every member select all members.
The runner also selects transitive dependents of changed members.
For directly changed members without dependent expansion, use a Git-ref filter such as --filter '[origin/main]'.
Use --changed-files-ignore-pattern <glob> to exclude matching changes.
Persist the patterns in lpm.toml > [workspace].changed-files-ignore-pattern.
Use --test-pattern <glob> to classify matching changes as tests.
If a member has only test changes, that member runs without dependent expansion.
Persist defaults in lpm.toml > [workspace].test-pattern.
Watch mode
lpm run build --watch
lpm run verify --watch --parallel --streamWatch mode runs one requested task and its prerequisites, then waits for changes. Each run finishes before the next starts. Changes during a run schedule a later run. Watch mode bypasses the task cache.
The watch scope includes each scheduled task's inputs and excludes declared outputs, .git, node_modules, and .lpm.
An operating system rescan request can trigger an extra run, even without a matching input path.
On macOS, LPM CLI also checks watched file metadata to recover missed change notifications. Checks normally run once per second. Large input trees use a longer interval to limit background work. These checks preserve the input and output rules.
Changes to package.json or lpm.json update the task graph and watch scope. Runtime-pin changes require a new watch session.
--watch cannot be combined with workspace selection, multiple requested task names, or --json.
For development servers, use lpm dev or the framework's watch mode.
Runtime and environment
The runner adds project and ancestor node_modules/.bin directories to PATH.
Managed runtimes follow the applicable project pins. See runtime selection for precedence and fallback behavior.
Environment selection uses --env, then tasks.<name>.env, then the matching env.<name> map.
Files load before local secrets. Schema defaults fill missing values, and invalid required values stop startup.
--no-env-check skips value checks and defaults for one invocation. Invalid schema declarations still stop the command.
Existing environment files and configured paths must remain inside the project.
lpm run build --env=production
lpm run build --no-env-checkSee local secrets and lpm env for selection, fallback, imports, and validation.
Common recovery steps
| Problem | Action |
|---|---|
| A requested task does not exist. | Define a package script or an lpm.json task before you retry. |
| A local run reports a workspace prerequisite. | Use --filter or --all from the workspace. |
| A cache entry never appears. | Check cache, declared outputs, and whether prerequisites have usable cache identities. |
| A changed file does not invalidate the cache. | Add its path to the task's input patterns. |
| Different checkout paths miss the cache. | Account for absolute script paths and executable identity in the cache key. |
| Watch reruns after generated output changes. | Declare the generated paths in outputs. |
For long-running development servers, readiness checks, and restarts, use lpm dev. Task watch mode expects each run to finish.
See also
lpm run— CLI surfacelpm dev— services + dev-server orchestrationlpm cache— task cache managementlpm.jsontasks — full task config reference- Workspaces —
--filtergrammar