Skip to main content

Introducing conda-tasks: the missing task runner for conda

· 6 min read
Jannis Leidel
Steering council member

Conda handles environments and packages well, but it has never had a built-in way to define project tasks -- the kind of thing you'd otherwise put in a Makefile, tox.ini, or a pile of shell scripts. Today we're releasing conda-tasks, a conda plugin that fills that gap. Define tasks in your project, wire up dependencies between them, and run everything through conda task (or the standalone ct command).

What it looks like

Create a conda.toml in your project root:

[tasks]
lint = "ruff check ."

[tasks.build]
cmd = "python -m build"
inputs = ["src/**/*.py", "pyproject.toml"]
outputs = ["dist/*.whl"]

[tasks.test]
cmd = "pytest tests/ -v"
depends-on = ["build"]

[tasks.check]
depends-on = ["test", "lint"]
description = "Run all checks"

Then run it:

conda task run check
# or, using the standalone CLI:
ct run check

conda-tasks resolves the dependency graph, runs build and lint first (in the right order), then test, and skips anything whose inputs haven't changed since the last run. That's it -- no new package manager, no new environment format. It works with your existing conda environments.

Why this exists

The conda ecosystem has had several tools for running commands from project definitions. anaconda-project was one of the first, combining conda environments with named project commands, platform-specific variants, and automatic environment setup. conda-project, its community successor, modernized that workflow with conda-lock integration. Both focus on reproducible project execution -- setting up environments and launching commands -- but neither supports dependencies between commands, caching, or templating.

On the other side, general-purpose Python task runners like tox, nox, and invoke work well for their respective use cases but don't integrate with conda environments or conda's plugin system.

pixi by prefix.dev changed things by shipping a full-featured task runner alongside its package manager: task dependencies with topological ordering, platform overrides, input/output caching, template variables, and task arguments. It demonstrated that a proper task system belongs in every project workflow.

conda-tasks brings that task runner to conda. The task system design is directly inspired by pixi's -- if you've used pixi run, the concepts will be familiar. The difference is scope: pixi manages both environments and tasks, while conda-tasks handles only the task-running side and leaves environment management to conda.

Features

Task dependencies

Tasks can depend on other tasks. conda-tasks resolves the full dependency graph using topological sorting and detects cycles:

[tasks]
lint = "ruff check ."

[tasks.compile]
cmd = "gcc -o main main.c"

[tasks.test]
cmd = "./main --test"
depends-on = ["compile"]

[tasks.check]
depends-on = ["test", "lint"]

Jinja2 templates

Commands support Jinja2 templates with context variables that expose conda's runtime state:

[tasks.info]
cmd = "echo Building on {{ conda.platform }} with conda {{ conda.version }}"

[tasks.clean]
cmd = "{% if conda.is_win %}rd /s /q build{% else %}rm -rf build/{% endif %}"

Available variables include conda.platform, conda.environment.name, conda.prefix, conda.is_win, conda.is_unix, conda.is_linux, conda.is_osx, and more.

Input/output caching

Specify inputs and outputs on a task, and conda-tasks will skip re-execution when inputs haven't changed. The cache uses a fast (mtime, size) pre-check before falling back to SHA-256 hashing, so the overhead on cache hits is minimal:

[tasks.build]
cmd = "python -m build"
inputs = ["src/**/*.py", "pyproject.toml"]
outputs = ["dist/*.whl"]

Platform overrides

Override task fields per platform, so the same task definition works across operating systems:

[tasks.clean]
cmd = "rm -rf build/"

[target.win-64.tasks]
clean = "rd /s /q build"

Task arguments

Tasks can accept named arguments with defaults, so you don't need separate task definitions for common variations:

[tasks.test]
cmd = "pytest {{ test_path }} -v"
args = [{ arg = "test_path", default = "tests/" }]
conda task run test src/tests/unit/

Environment targeting

Run tasks in any conda environment using the same -n/-p flags you already use with conda:

conda task run test -n py311-compat

Tasks can also declare a default-environment so they always run in the right place:

[tasks.test]
cmd = "pytest tests/ -v"
default-environment = "py311-compat"

Multiple file formats

conda-tasks reads task definitions from four file formats, checked in this order:

  1. pixi.toml -- reads the [tasks] table directly
  2. conda.toml -- conda-native TOML format
  3. pyproject.toml -- reads [tool.conda.tasks], [tool.conda-tasks.tasks], or [tool.pixi.tasks]
  4. .condarc -- reads plugins.conda_tasks.tasks via conda's settings API

If you already have tasks in a pixi.toml, conda-tasks can read them as-is. You can also export them to the canonical format:

conda task export --file pixi.toml

Install and try it

conda install --channel conda-forge conda-tasks

Then create a conda.toml and run your first task:

conda task run <task-name>
conda task list
# or use the standalone CLI:
ct run <task-name>
ct list

What it doesn't do

conda-tasks is a task runner, not a package manager. It does not create environments or install dependencies -- that's conda's job. If you're coming from pixi where pixi run handles both, see the migration guide.

Get involved

conda-tasks is a conda-incubator project. Contributions, bug reports, and feature requests are welcome:

Acknowledgements

conda-tasks would not exist without the work of the prefix.dev team on pixi. The task system design -- dependency graphs, platform overrides, caching, template variables, and the overall developer experience -- comes directly from their implementation. We're grateful for their contribution to the conda ecosystem and for demonstrating what a project task runner should look like.

Thanks also to the anaconda-project and conda-project teams for exploring project-level automation in the conda ecosystem long before conda-tasks existed.

Further reading