:focal(smart))
Never tried Pixi? Grab the latest binary and spin up a project in seconds: pixi.sh/latest/.
A fun meta‑detail: Pixi manages its own repository with Pixi. We migrated the project’s colossal manifest to the new parameterised style. Eating our own dog food meant every edge case surfaced instantly—and shaped the design you’re about to see.
The sections below break down why the change matters, how the new syntax works, and the practical wins you’ll feel the next time you open a bloated manifest.
Every time you add a new test folder or tweak a flag you duplicate a whole task line, watch the file balloon, and hope you’ve changed every instance consistently.
With argument appends, any flags you type after the task name are simply concatenated to the end of the command at run‑time. This works for quick, ad‑hoc tweaks, but it offers no structure or visibility inside the manifest.
Call with no arguments - both names take their defaults:
Call with one positional - binds to subset, report keeps its default:
Call with two positionals - overrides both:
Rule of thumb: The prefix of args that you pass on the CLI is matched in order; arguments that are not filled will be set by the defaults.
Scalability – a single parameterised definition can cover dozens of use‑cases without extra copy‑paste.
Self‑documenting – the args array acts like a function signature, so users can see required and optional parameters at a glance.
Improved diagnostics – Pixi validates missing or extra arguments before executing, surfacing clearer error messages.
Greater flexibility – variables can appear anywhere in the task (cmd, inputs, outputs, or depends-on), not just at the end of the command line.
Arguments move through dependency chains the same way—first match positionally, then apply defaults.
Here, running the ci task, invokes two test executions — one for the unit tests and one for integration tests.
Notice that the integration tests use the --quiet flag whereas the unit tests use the default value of --verbose.
Aliases are just syntactic sugar for calling a task with arguments:
Under the hood Pixi expands these to full tasks whose body is simply an invocation of lint—no extra boilerplate.
Pixi uses MiniJinja — a lightweight, Rust‑native implementation of the familiar Jinja2 engine—to render strings before a task runs. Any string field in a task (cmd, inputs, outputs, even values inside depends-on) can reference variables, apply filters, and include simple control flow.
Placeholders {{ var }} – injects the value of a task argument.
Filters {{ list | join(',') }} – pipe a value through built‑in filters like join, upper, replace, etc.
Conditionals & loops – {% if %}, {% for %} blocks for more advanced cases.
Full UTF‑8 & quoting rules – MiniJinja handles escaping, so your paths stay valid on all platforms.
Running with pixi run test unit true toggles coverage collection without duplicating the task whereas pixi run test unit does not output the coverage.
Parameterising tasks deliver tangible day‑to‑day wins:
DRY manifests – one canonical task + many tiny aliases instead of a sea of copy‑paste.
Safer edits – change the template once; every variant inherits the fix.
Clear intent – args arrays read like function signatures, so newcomers grasp what matters at a glance.
Re‑usable pipelines – pass different arguments in CI matrices without redefining whole tasks.
Environment flexibility – mix dev, staging, and prod dependencies in a single workflow.
Faster onboarding – smaller, self‑documenting manifests reduce the “where do I add my task?” friction.
Manifest‑level variables ([vars] )– declare values like profile="--release" once and reference them everywhere with {{ profile }}.
During the past four months I’ve had the privilege to dive deep into Pixi, ship features that reached users almost immediately, and refine them in real time thanks to the community’s feedback. I’m endlessly grateful for the steady guidance, code reviews, and encouragement from every member of the Prefix team. Your support turned my first real exposure to the package‑management world into a series of tangible wins—and has me excited for Pixi’s next set of features.
[tasks.test]
cmd = "pytest tests/unit"# Appends everything after the task name
pixi run test --verbose --maxfail=1 # pytest tests/unit --verbose --maxfail=1pixi run test # pytest all --junit-xml reports/junit.xml -qpixi run test integration # pytest integration --junit-xml reports/junit.xml -qpixi run test unit tmp/out.xml # pytest unit --junit-xml tmp/out.xml -q[tasks.test]
cmd = "pytest {{ subset }}{% if coverage %} --cov --cov-report=xml{% endif %} -q"
args = ["subset", { arg = "coverage", default = "" }][tasks]
test-unit = "pytest tests/unit"
test-integration = "pytest tests/integration"
benchmarks = "pytest tests/bench"
lint-fast = "ruff --fix src/"
lint-strict = "ruff src/ --select I001"[tasks.test]
cmd = "pytest {{ subset }} --junit-xml {{ report }} -q"
args = [{ arg = "subset", default = "all" }, { arg = "report", default = "reports/junit.xml" }][tasks.test]
cmd = "pytest {{ dir }} {{ flags }}"
args = ["dir", { arg = "flags", default = "--verbose" }]
[tasks.ci]
depends-on = [
{ task = "test", args = ["unit"] },
{ task = "test", args = ["integration", "--quiet"] }
][tasks]
lint-fast = [{ task = "lint", args = ["--fix"] }]
lint-strict = [{ task = "lint", args = ["--select I001"] }]