Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cachix/devenv/llms.txt

Use this file to discover all available pages before exploring further.

Tasks in devenv allow you to define units of work that form a dependency graph (a DAG). devenv schedules them in parallel wherever possible, respects dependency ordering, and can cache results to skip work that hasn’t changed. Tasks integrate naturally with processes and the shell lifecycle via built-in enterShell and enterTest hooks.

Defining Tasks

Tasks are defined under tasks in devenv.nix. The key is a namespaced name using colons:
devenv.nix
{ pkgs, ... }:

{
  tasks."myapp:hello" = {
    exec = ''echo "Hello, world!"'';
  };
}
Run a task with:
$ devenv tasks run myapp:hello
Running tasks     myapp:hello
Succeeded         myapp:hello         9ms
1 Succeeded                           10.14ms
You can also run all tasks in a namespace by providing just the namespace prefix:
$ devenv tasks run myapp
Running tasks     myapp:hello myapp:build myapp:test
Succeeded         myapp:hello           9ms
Succeeded         myapp:build         120ms
Succeeded         myapp:test          350ms
3 Succeeded                           479.14ms

Dependencies Between Tasks

Declare edges between tasks using before or after:
  • after = [ "other" ] — run this task after other (makes other an upstream dependency).
  • before = [ "other" ] — run this task before other (makes this task an upstream dependency of other).
before and after describe the same edge from opposite ends, so you can declare a dependency from whichever side is more convenient. These are equivalent:
devenv.nix
{
  # declared from the dependent task
  tasks."myapp:build".after = [ "myapp:generate" ];

  # ...is the same edge as declaring it from the dependency
  tasks."myapp:generate".before = [ "myapp:build" ];
}

Dependency States

A dependency waits for its target to reach a particular state before it is considered satisfied. Append an @ suffix to choose the state explicitly:
SuffixSatisfied whenFailure propagates?
@startedthe target has begun executingyes
@readya process passes its readiness probe; for tasks this means successyes
@succeededthe target exits with code 0 (or is skipped)yes
@completedthe target finishes, regardless of exit codeno (soft dependency)
When no suffix is given, the default is @ready for processes and @succeeded for one-shot tasks. A common use is running a setup task once a service is ready:
devenv.nix
{
  tasks."myapp:configure" = {
    exec = "create-buckets";
    after = [ "devenv:processes:garage@ready" ];
  };
}

Execution Modes

When you run a task, devenv schedules a subgraph around it rather than only that one task. --mode controls how much of the graph is included:
ModeRuns
singleonly the named task
before (default)the task and everything upstream of it
afterthe task and everything downstream of it
allthe entire connected graph, both upstream and downstream
$ devenv tasks run myapp:build               # before mode (default): build + its dependencies
$ devenv tasks run myapp:build --mode single # just build
$ devenv tasks run myapp:build --mode all    # build, its dependencies, and its dependents
devenv up starts processes in before mode, while devenv test runs in all mode. This difference matters for setup tasks attached to processes — see Processes as tasks below.

enterShell and enterTest Integration

devenv:enterShell and devenv:enterTest are built-in lifecycle events that run tasks at specific points:
  • devenv:enterShell runs before the shell is entered (devenv shell) and before processes start (devenv up).
  • devenv:enterTest runs before tests execute (devenv test). It depends on devenv:enterShell, so all shell setup tasks run first automatically.
To hook into these events, use before to declare that your task should run before the event completes:
devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "bash:hello" = {
      exec = "echo 'Hello world from bash!'";
      before = [ "devenv:enterShell" ];
    };

    "myapp:test-setup" = {
      exec = "echo 'Preparing test fixtures...'";
      before = [ "devenv:enterTest" ];
    };
  };
}
$ devenv shell
...
Running tasks     devenv:enterShell
Succeeded         devenv:git-hooks:install  25ms
Succeeded         bash:hello                 9ms
Succeeded         devenv:enterShell         13ms
3 Succeeded                                 28.14ms
Many devenv modules automatically hook into these events. For example, enabling git hooks registers devenv:git-hooks:install as a dependency of devenv:enterShell.

Using Another Language for Task Execution

Tasks can use a specific package as their interpreter, for example a Python package:
devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "python:hello" = {
      exec = ''
        print("Hello world from Python!")
      '';
      package = config.languages.python.package;
    };
  };
}

Caching with status

If you define a status command, it will be executed first. If it returns 0, the exec command is skipped. When a task is skipped, outputs from the most recent successful run are restored and passed to dependent tasks.
devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "myapp:migrations" = {
      exec = "db-migrate";
      status = "db-needs-migrations";
    };
  };
}

Running Only When Files Have Changed

Use execIfModified to specify a list of files or glob patterns to monitor. The task only runs if any of these files have been modified since the last successful run:
devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "myapp:build" = {
      exec = "npm run build";
      execIfModified = [
        "src/**/*.ts"  # All TypeScript files in src directory
        "*.json"       # All JSON files in the current directory
        "package.json" # Specific file
        "src"          # Entire directory
      ];
      # Optionally run the build in a specific directory
      cwd = "./frontend";
    };
  };
}
The system tracks both file modification times and content hashes to detect actual changes. When a task is skipped due to no file changes, any previous outputs from that task are preserved and passed to dependent tasks.

Inputs and Outputs

Tasks support passing inputs and producing outputs, both as JSON objects. The following environment variables are available inside a task’s exec script:
  • $DEVENV_TASK_INPUT — JSON object from tasks."myapp:mytask".input
  • $DEVENV_TASKS_OUTPUTS — JSON object with dependent task names as keys and their outputs as values
  • $DEVENV_TASK_OUTPUT_FILE — writable file where the task writes its JSON output
  • $DEVENV_TASK_EXPORTS_FILE — writable file for exporting environment variables to dependent tasks (write name\0base64(value)\0 pairs)
devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "myapp:mytask" = {
      exec = ''
        echo $DEVENV_TASK_INPUT > $DEVENV_ROOT/input.json
        echo '{ "output": 1 }' > $DEVENV_TASK_OUTPUT_FILE
        echo $DEVENV_TASKS_OUTPUTS > $DEVENV_ROOT/outputs.json
      '';
      input = {
        value = 1;
      };
    };
  };
}

Shell Messages

Tasks can display messages to the user when entering the shell by writing a devenv.messages array to $DEVENV_TASK_OUTPUT_FILE:
devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "myapp:info" = {
      exec = ''
        echo '{"devenv":{"messages":["Setup complete. Dashboard: http://localhost:3000"]}}' > "$DEVENV_TASK_OUTPUT_FILE"
      '';
      before = [ "devenv:enterShell" ];
    };
  };
}
Messages are printed after the shell environment is loaded, so they remain visible in the interactive session.

Passing Inputs from the CLI

You can override or add inputs when running tasks from the command line using --input and --input-json:
$ devenv tasks run myapp:mytask --input value=42 --input name=hello
Values are automatically parsed as JSON when valid, otherwise treated as strings. For example, --input count=3 sets a number, --input flag=true sets a boolean, and --input name=hello sets a string. You can also pass a full JSON object:
$ devenv tasks run myapp:mytask --input-json '{"value": 42, "name": "hello"}'
Both flags can be combined. --input-json is applied first, then individual --input values are merged on top.

Processes as Tasks

All processes defined in processes are automatically available as tasks with the devenv:processes: prefix. This allows you to:
  • Run individual processes as tasks
  • Define dependencies between tasks and processes
  • Use before/after edges connecting tasks and processes interchangeably
devenv.nix
{ pkgs, ... }:

{
  # Define a process
  processes.web-server = {
    exec = "python -m http.server 8080";
  };

  # Define a task that runs before the process
  tasks."app:setup-data" = {
    exec = "echo 'Setting up data...'";
    before = [ "devenv:processes:web-server" ];
  };
}
When you run devenv tasks run devenv:processes:web-server, it will:
  1. First run any tasks that have before = [ "devenv:processes:web-server" ]
  2. Then execute the process itself
You can also run tasks after a process finishes by depending on its @completed state:
devenv.nix
{ pkgs, ... }:

{
  processes.app-server = {
    exec = "node server.js";
  };

  tasks."app:cleanup" = {
    exec = ''
      echo "Server stopped, cleaning up..."
      rm -f ./server.pid
      rm -rf ./tmp/cache/*
    '';
    after = [ "devenv:processes:app-server@completed" ];
  };
}
A task wired downstream of a process (e.g., with after = [ "devenv:processes:<name>" ]) is skipped when you run devenv up, because devenv up uses before mode and only schedules upstream dependencies. Use devenv up --mode all to include downstream tasks, or rely on devenv test which already runs in all mode.

Git Integration

Tasks can reference the git repository root path using ${config.git.root}, which is useful in monorepo environments:
devenv.nix
{ config, ... }:

{
  tasks."build:frontend" = {
    exec = "npm run build";
    cwd = "${config.git.root}/frontend";
  };

  tasks."test:backend" = {
    exec = "cargo test";
    cwd = "${config.git.root}/backend";
  };
}
This allows tasks to reference paths relative to the repository root regardless of where the devenv.nix file is located within the repository.

Build docs developers (and LLMs) love