Back to core workflows Fix dependency resolution Tune package metadata Jump to monorepo patterns

Fixing 'npm run: script not found' in a Workspace

Running a script from the repo root fails with 'Missing script' even though the script exists in a package. This page explains why the root has no such script and how to route the command to the right package.

Exact symptoms and error messages

Exact symptoms and error messages Exact symptoms and error messages in production JavaScript package workflows. Exact symptoms and error messages Exact symptoms and error messages in production JavaScript package workflows.
Exact symptoms and error messages — the core idea of this section at a glance.
$ npm run build
npm error Missing script: "build"
npm error To see a list of scripts, run: npm run

Root cause analysis

npm run looks in the current package's scripts, and the repo root manifest often has no build — the script lives in each workspace package. Without a workspace-aware runner or a --workspace flag, npm never descends into the packages. This is the scope boundary described in Root-Level vs Package-Level Scripts and governed by the workspace configuration.

Root cause analysis npm run looks in the current package's scripts, and the repo root manifest often has no build — the script lives in each Root cause analysis npm run looks in the current package's scripts, and the repo root manifest often has no build — the script lives in each workspace package.
Root cause analysis — the core idea of this section at a glance.

npm run is scoped to the current package's scripts, and at the repo root that manifest often defines only orchestration, not a build. The script you want lives in each workspace package, and without a --workspace flag or a workspace-aware runner, npm never descends into them — so it correctly reports the script missing from the manifest it actually looked at. The mismatch is between where you ran the command and where the script is defined.

This trips people migrating from a single-package repo, where npm run build at the root always worked because there was only one manifest. In a workspace, the root and the packages are distinct scopes, and commands have to say which scope they mean. Understanding that boundary — root scripts orchestrate, package scripts do the work — is the core of Root-Level vs Package-Level Scripts.

Resolution and configuration patch

Target the packages explicitly, or add a root delegating script:

Resolution and configuration patch Target the packages explicitly, or add a root delegating script: Resolution and configuration patch Target the packages explicitly, or add a root delegating script:
Resolution and configuration patch — the core idea of this section at a glance.
# npm: run build in every workspace that defines it
npm run build --workspaces --if-present
# pnpm: run recursively
pnpm -r run build
// root package.json — a delegating script
{
  "scripts": {
    "build": "pnpm -r run build"
  }
}

CLI validation and debug commands

CLI validation and debug commands CLI validation and debug commands in production JavaScript package workflows. CLI validation and debug commands CLI validation and debug commands in production JavaScript package workflows.
CLI validation and debug commands — the core idea of this section at a glance.
# List scripts the current package actually defines
npm run
# Confirm which packages define build
pnpm -r exec node -e "console.log(require('./package.json').scripts?.build || 'none')"

Prevention and CI guardrails

  • Add root-level delegating scripts for common tasks so npm run build works from the root.
  • Use --if-present so packages without the script are skipped, not errored.
  • Prefer a task runner (turbo run build) that resolves the graph automatically.
  • Document which scripts are root-level orchestration vs package-level.
Prevention and CI guardrails Prevention and CI guardrails in production JavaScript package workflows. Prevention and CI guardrails Prevention and CI guardrails in production JavaScript package workflows.
Prevention and CI guardrails — the core idea of this section at a glance.

Root delegating scripts versus per-package scripts

The cleanest resolution is to give the root a thin delegating script for each common task, so npm run build at the root does the obvious thing while the real work stays in the packages. The root script fans the command out; each package's script builds itself.

Delegating root A root script fans the task out to each package. root run build one entry pnpm -r run build fan out each package builds own script
A thin root script gives one entry point while packages own the work.
// root package.json
{
  "scripts": {
    "build": "pnpm -r run build",
    "test": "pnpm -r run test",
    "lint": "turbo run lint"
  }
}

This keeps the two scopes distinct but connected: contributors get one root entry point per task, while each package remains responsible for its own implementation. The alternative — targeting a package explicitly with --workspace=pkg or --filter pkg — is right for one-off, package-specific commands, but for everyday tasks the delegating root script is what makes the repo approachable.

Using --if-present so a partial script set doesn't error

In a real workspace, not every package defines every script — a docs package has no build, a types-only package has no test. A fan-out command that errors on the first missing script is brittle, so tell the runner to skip packages that lack it.

Missing-script handling Erroring on a missing script versus skipping it. Without --if-present • errors on first miss • command aborts • needs no-op stubs With --if-present • skips missing script • completes across the rest • packages differ freely
--if-present lets a heterogeneous workspace run one task without no-op stubs.
# Skip packages without the script instead of failing
npm run build --workspaces --if-present
pnpm -r run test        # pnpm skips missing scripts by default

--if-present (npm) and pnpm's default recursive behavior treat a missing script as 'nothing to do here' rather than an error, so the command completes across the packages that do define it. This is what lets a heterogeneous workspace — where packages legitimately differ in which scripts they have — run a single root task without every package needing a no-op stub for tasks it does not perform.

Frequently Asked Questions

Why does --if-present matter?

In a workspace, not every package defines every script. --if-present skips packages that lack the script instead of failing the whole command, so a partial script set does not break the run.

Should build scripts live at the root or in packages?

Keep the real build in each package and a thin delegating script (or task-runner target) at the root. That preserves per-package scope while giving one root entry point.

Why does npm run build fail at the repo root?

Because npm run looks in the current package's scripts, and the root manifest often has no build — the script lives in each workspace package. Add a delegating root script or target the packages with --workspaces/--filter.

How do I run a task across packages that don't all define it?

Use --if-present with npm (npm run build --workspaces --if-present) or pnpm's recursive run, which skips missing scripts by default. Packages without the script are skipped rather than erroring the whole command.

Should the real build live at the root or in each package?

Keep the real build in each package and put a thin delegating script (or task-runner target) at the root. That preserves per-package scope while giving contributors one root entry point per task.

Related

Root-Level vs Package-Level Scripts