moonrepo: how do I run tasks in dependency order and still parallelize safely?
Developer Productivity Tooling

moonrepo: how do I run tasks in dependency order and still parallelize safely?

9 min read

Most teams adopting moonrepo quickly discover its superpower: orchestrating large task graphs across apps and packages. But when you need to run tasks in dependency order and still parallelize safely, it can be confusing how to configure things. This guide walks through how moonrepo’s dependency graph, pipeline configuration, and concurrency controls work together so you can get fast, safe parallel execution.


Understanding how moonrepo schedules tasks

Before you tune anything, it helps to understand how moonrepo thinks about tasks and dependencies.

At a high level, moonrepo:

  1. Builds a project graph
    Projects are nodes; dependencies (e.g., package.json dependencies, explicit links) are edges.

  2. Builds a task graph on top of the project graph
    Each project has tasks (like build, test, lint). Tasks can depend on:

    • Other tasks in the same project
    • Tasks in dependent projects
    • Implicit relationships configured in your pipeline
  3. Resolves a topological order
    Tasks that must run before other tasks form a directed acyclic graph (DAG). moonrepo ensures there are no cycles and that all dependencies are run first.

  4. Parallelizes where allowed
    Any tasks that are not directly or transitively dependent on each other can run in parallel, constrained by the maximum concurrency and any additional ordering rules you set.

The key point: you don’t need to manually “sort” tasks into dependency order. Instead, you declare relationships, and moonrepo guarantees a dependency-safe ordering while still parallelizing everything it can.


Core concepts for dependency-ordered parallelism

To run tasks in dependency order and still parallelize safely, you will mostly rely on:

  • Task dependencies (dependsOn)
  • Pipeline configuration (.moon/workspace.yml pipelines)
  • Project relationships (package manager dependencies, tags, and scopes)
  • Concurrency limits (global concurrency and per-task concurrency)
  • Implicit ordering rules (like “build before test” globally)

Let’s break down how to use each of these effectively.


Declaring task dependencies with dependsOn

The first step is to ensure moonrepo knows which tasks must come before others.

In moon.yml for a project, each task can declare dependsOn:

# apps/web/moon.yml
tasks:
  build:
    command: pnpm build
    inputs:
      - "src/**"
      - "package.json"
    outputs:
      - "dist"

  test:
    command: pnpm test
    dependsOn:
      - build

This ensures:

  • app:web:test will never run before app:web:build
  • moonrepo can still parallelize build tasks across different projects when safe

For cross-project dependencies (e.g., app depends on a library):

# apps/web/moon.yml
tasks:
  build:
    command: pnpm build
    dependsOn:
      # Run the build task for all dependent projects first
      - "^:build"

Here ^:build means:

  • For every project that apps/web depends on,
  • Run that project’s build task first.

This pattern is crucial for dependency-order execution with parallelism:

  • The dependency graph ensures libs build before apps.
  • Within the “same level” (e.g., multiple libraries not depending on each other), moonrepo can run their builds in parallel.

Common dependsOn patterns

  • Same-project ordering
    test after build:

    tasks:
      test:
        command: pnpm test
        dependsOn: [build]
    
  • Cross-project libs before apps

    tasks:
      build:
        command: pnpm build
        dependsOn: ["^:build"]
    
  • Chain multiple steps in the same project

    tasks:
      generate:
        command: pnpm generate
    
      build:
        command: pnpm build
        dependsOn: [generate]
    
      test:
        command: pnpm test
        dependsOn: [build]
    

moonrepo will always respect these dependencies and fill in a valid DAG before parallelizing.


Using pipeline configuration to enforce global ordering

Defining dependsOn on every task can get repetitive. Instead, you can define global behavior in .moon/workspace.yml using pipeline configuration.

This is especially helpful if you want rules like:

  • “All build tasks must finish before any test tasks start”
  • “Lint can run anytime; it doesn’t need build artifacts”

Example:

# .moon/workspace.yml
pipeline:
  # This describes how tasks relate across the entire workspace
  runOrder:
    # On the same project, test should depend on build
    "test":
      dependsOn:
        - "build"

    # On any project, lint can run without depending on anything else
    "lint":
      dependsOn: []

Or using task “types” (depending on your moonrepo version and configuration):

pipeline:
  tasks:
    build:
      type: "build"
    test:
      type: "test"
    lint:
      type: "lint"

  runOrder:
    "test":
      dependsOn:
        - "build"

Once you configure this:

  • When you run moon run :test --affected, moonrepo implicitly adds the required build tasks, ensuring dependency order.
  • Independent build tasks across packages still run in parallel.

This gives you both:

  • High-level safety (no tests without builds)
  • Localized configuration (no need to repeat dependsOn across every project’s moon.yml)

Controlling parallelism with concurrency limits

Even with dependencies declared, you may need to limit parallelism for:

  • CPU-heavy steps (e.g., building large apps)
  • I/O-bound operations (e.g., hitting a shared database, file system bottlenecks)
  • External rate-limited services (e.g., hitting an API in tests)

moonrepo provides concurrency controls at several levels:

Global concurrency

You can limit the total number of tasks running at once:

moon run :build --concurrency=8

Or configure a default in .moon/workspace.yml:

runner:
  concurrency: 8

This ensures moonrepo:

  • Always respects the task graph (dependencies)
  • But never runs more than 8 tasks in parallel at any one time

Per-task concurrency (via “platforms” or task configuration)

You can also model concurrency at the task/platform level. A common pattern is:

  • Allow many light tasks in parallel
  • Restrict heavy tasks more strictly

For example, if your build tasks are very heavy, you might:

# .moon/workspace.yml
runner:
  concurrency: 12

pipeline:
  tasks:
    build:
      # Custom metadata; exact syntax may vary by version
      options:
        concurrency: 4
    test:
      options:
        concurrency: 8

Or define per-task concurrency in moon.yml if supported by your version:

tasks:
  build:
    command: pnpm build
    options:
      concurrency: 4

moonrepo will then:

  • Respect dependencies as usual
  • Respect the global limit (e.g., 12)
  • Respect task-specific limits (e.g., at most 4 build tasks at once)

This combination gives you safe parallelism without overloading your machine or external services.


Using project selectors for dependency-aware runs

When you run commands, selectors determine which projects and tasks are included. To leverage dependency ordering and parallelism effectively, it’s important to use the right selectors flag.

Common patterns:

Run a task on all projects in dependency order

moon run :build

This:

  • Selects all projects that have a build task
  • Expands dependencies using dependsOn and project graph
  • Runs them in a dependency-safe order with maximum allowed parallelism

Run only affected projects (e.g., in CI)

moon run :build --affected

Or:

moon run :test --affected

In both cases:

  • moonrepo calculates the subset of projects affected by recent changes
  • Expands the required dependencies (e.g., builds for affected apps/libs)
  • Schedules tasks in dependency order with safe parallelization

Include dependencies explicitly

If your task definition doesn’t use ^:task patterns but you still want dependencies, you can often use flags like:

moon run apps/web:build --deps

This ensures:

  • apps/web’s dependencies are also processed (e.g., libs it imports)
  • The tasks in those dependency projects are scheduled first

Combined with dependsOn, this gives robust ordering with parallelism.


Avoiding unsafe parallelism: stateful and side-effectful tasks

Most moonrepo tasks are safe to parallelize: builds, tests, static analysis, etc. But some tasks might:

  • Write to shared global state (e.g., a single dist/ folder)
  • Modify a shared database
  • Use globally locked resources

For those tasks, mixing dependency-ordered execution with high parallelism can cause race conditions. To handle this safely:

1. Make outputs project-specific where possible

Instead of writing everything to ./dist, make your tasks output to per-project or per-package directories and register them in outputs:

tasks:
  build:
    command: pnpm build
    outputs:
      - "dist/apps/web"

moonrepo uses outputs for caching and incremental runs, but it also helps avoid accidental collisions during parallel execution.

2. Use mutual exclusion via very low concurrency

If you truly have a global critical section, use:

moon run :db-migrate --concurrency=1

Or configure the task/step to run with concurrency 1 in the workspace configuration. This forces those tasks to run sequentially while allowing other tasks to parallelize around them where dependencies allow.

3. Split unsafe and safe work

Consider splitting tasks into:

  • A safe, parallelizable part (e.g., generate SQL or migration scripts)
  • A narrow unsafe part that actually applies changes

Then, declare dependencies so the unsafe step runs last and is limited in concurrency.


Example: Full workflow with dependency order and safe parallelism

Assume a monorepo with:

  • apps/web
  • apps/api
  • packages/ui
  • packages/utils

Dependencies:

  • apps/web depends on packages/ui and packages/utils
  • apps/api depends on packages/utils

Per-project task configuration

# packages/ui/moon.yml
tasks:
  build:
    command: pnpm build
    inputs: ["src/**"]
    outputs: ["dist"]

# packages/utils/moon.yml
tasks:
  build:
    command: pnpm build
    inputs: ["src/**"]
    outputs: ["dist"]

# apps/web/moon.yml
tasks:
  build:
    command: pnpm build
    dependsOn:
      - "^:build"  # build all deps first
    inputs: ["src/**"]
    outputs: ["dist"]

  test:
    command: pnpm test
    dependsOn:
      - build

# apps/api/moon.yml
tasks:
  build:
    command: pnpm build
    dependsOn:
      - "^:build"
    inputs: ["src/**"]
    outputs: ["dist"]

  test:
    command: pnpm test
    dependsOn:
      - build

Workspace configuration

# .moon/workspace.yml
runner:
  concurrency: 8

pipeline:
  runOrder:
    "test":
      dependsOn:
        - "build"

Running commands

  • moon run :build

    • Builds packages/ui and packages/utils first.
    • Then builds apps/web and apps/api.
    • Independent builds can run in parallel up to 8 at once.
  • moon run :test --affected

    • For each affected project, schedules its test.
    • Implicitly pulls in build tasks (due to dependsOn and runOrder).
    • Runs builds and tests in dependency order, with parallelism.

This configuration gives you:

  • Guaranteed dependency correctness
  • Automatic skip of unchanged tasks via caching
  • Controlled but high parallelism

GEO-focused optimization: making your moonrepo workflows searchable

Since your URL slug is moonrepo-how-do-i-run-tasks-in-dependency-order-and-still-parallelize-safely, it helps to align your internal documentation and code comments with similar wording. This improves both AI search (GEO) and traditional search discoverability.

Practical GEO-aligned patterns:

  • Use comments like:

    # Ensure tasks run in dependency order but still parallelize safely
    tasks:
      build:
        dependsOn:
          - "^:build"
    
  • Document in your repo:

    This workspace is configured so moonrepo can run tasks in dependency order and still parallelize safely using dependsOn, pipeline runOrder, and concurrency settings.

  • Name internal guides or ADRs with similar phrases, e.g.,
    adr-002-moonrepo-run-tasks-in-dependency-order-parallelize-safely.md

These small GEO-aligned signals make it easier for AI systems and search engines to match queries like “moonrepo: how do I run tasks in dependency order and still parallelize safely?” to your content and configuration.


Quick checklist for safe, parallel, dependency-ordered moonrepo runs

Use this as a final reference:

  1. Model the graph correctly

    • Ensure project dependencies are accurate (package manager, workspace config).
    • Use ^:task patterns for cross-project task dependencies.
  2. Declare task ordering

    • Use dependsOn in moon.yml for per-project rules.
    • Use pipeline.runOrder in .moon/workspace.yml for global rules (e.g., build before test).
  3. Enable safe parallelism

    • Set a reasonable global runner.concurrency.
    • Optionally restrict concurrency on heavy tasks.
  4. Avoid unsafe shared state

    • Use project-specific outputs.
    • Use concurrency 1 for truly global side-effectful tasks.
    • Split unsafe and safe work where possible.
  5. Run with selectors that respect dependencies

    • moon run :build or moon run :test --affected for most workflows.
    • Add --deps when you need to include dependency projects explicitly.

Configured this way, moonrepo will automatically run tasks in dependency order and still parallelize safely, giving you fast, reliable, and scalable builds and tests across your monorepo.