Tasks
Tasks are defined in a minimal.toml file, and describe
a runtime environment + command invocations to be executed using mip run <taskname>
Tasks schema
Tasks are defined in a [tasks.<task-name>] block in your minimal file.
packages - Packages to be installed in the runtime environment
Optional
packages lists additional packages which will be installed in the tasks’
runtime environment. Packages listed here are in addition to any installed due to the stack.
[tasks.my_task]
packages = ["python"] # Additionally install python
exec or bash - What the task should do
exec describes a command or invocation that should run when the task is launched. exec can be
defined either as an argv string:
[tasks.my_task]
exec = "pnpm build"
or as a program and its list of arguments:
[tasks.my_task]
exec = ["pnpm", "build"]
When exec names a command without an absolute path (or a ./ prefix), the
command is resolved to /bin/<command> inside the sandbox; in the examples
above, pnpm runs as /bin/pnpm.
bash describes a bash command that should run when the task is launched.
[tasks.my_task]
bash = "echo \"hello\" > hello.txt"
A third action, echo, prints a fixed string without composing a sandbox at
all; useful for pointers and reminders:
[tasks.docs]
echo = "Docs live at https://docs.minimal.dev"
When args are set on the task, arguments can be substituted into the invocation using Nickel’s string interpolation syntax:
[tasks.greet]
args.name = "string"
bash = "echo \"Hello %{name}!\""
description - Describe the task
Optional
description is a free-text description of the task, shown alongside the task
name in mip status.
[tasks.my_task]
description = "Run the dev server with hot reload"
state_key - Persist state between invocations
Optional
state_key controls caching of build artifacts and files between runs.
[tasks.my_task]
state_key = "dev" # Cache build artifacts under 'dev'
env_vars - Environment variables to set
Optional. env_vars is an alias of the canonical vars key; both parse
env_vars sets environment variables in the tasks’ runtime environment. Variables
set here take precedence over any inherited from the stack.
[tasks.my_task]
env_vars.CC = "gcc"
env_vars.AWS_PROJECT = "zest"
Environment variables can also inherit their value from the parent process. To do this,
declare the variable with the value { inherit = true }:
[tasks.my_task]
env_vars.TOKEN = { inherit = true }
The parent process is the shell you run min task run in: the value is read
on the client, at the moment you invoke the task, and carried to the session —
the same way [session.vars] resolves at activation. So exporting the variable
in your shell is enough, and the daemon’s own environment is never consulted.
A variable declared inherit but not set in that shell is an error, reported
before the task’s session is created — unless your policy ignores the name, in
which case it is dropped rather than looked up (below):
$ min task run deploy
error: task 'deploy' declares `env_vars.TOKEN = { inherit = true }`, but TOKEN
is not set in this shell; export it before running the task
This applies to min task run. Two other ways of running a declared task
still resolve inherit against the daemon’s environment:
min run <task>, from inside a session, has no invoking client to read from.min session run <session> <task>runs against a session you already have, over the attach transport, which forwards onlyMINIMAL_SESSION_IDand your locale.
So prefer an explicit value for tasks meant to be run either of those ways.
Environment variables and your policy
Resolving on the client means reading your shell. A project’s
minimal.toml naming env_vars.AWS_SECRET_ACCESS_KEY = { inherit = true } is
that project asking for your credential, so every name a task declares —
inherited or literal — is checked against the [vars] section of your
user_policy.toml before its value is read:
- a name matching
denyfails the run, naming the rule’s file. - a name matching
ignoreis dropped and the task runs without it. The name is removed from the task’s declarations outright, so this holds for a literal value as much as an inherited one, and an ignored variable does not have to be set. - a name matching
allowis carried. - anything else is not carried. On a terminal you are asked, once per name, and can record the answer as a rule; anywhere else the run stops and prints the rules to add.
This is the same gate [session.vars] from the same file has always passed
through, with the same precedence — deny, then ignore, then allow — and
allow is required for the same reason: a project you have not read should
not be able to name a variable and receive its value.
An echo task is the one exception, and it is not a hole: its output comes
straight from the declaration without an environment being built, so its
env_vars are read by nobody. Nothing is looked up, so there is nothing to
gate — an echo task never prompts, and never fails over a variable it has
no use for.
# user_policy.toml
[vars]
allow = ["ZZ_TASK_*", "RUST_*"]
deny = ["AWS_*"]
A denied name:
$ min task run deploy
error: task 'deploy' declares `env_vars.AWS_SECRET_ACCESS_KEY`, but
AWS_SECRET_ACCESS_KEY is denied by `[vars] deny` in
~/.config/minimal/user_policy.toml; remove the declaration, or the deny rule
if this task should see it
An unlisted name in CI, where there is no one to ask:
$ min task run deploy
error: task 'deploy' declares 1 environment variable that your policy does not
allow, and stdin/stderr is not a terminal.
Add the following to ~/.config/minimal/user_policy.toml:
[vars]
allow = ["DEPLOY_TARGET"]
Then re-run this command.
interactive - TUI apps and shells
Optional, Default false
interactive indicates that this task must be run interactively (that is, with standard input and a tty connected).
[tasks.my_task]
interactive = true
patches - Map in files/directories from the system
Optional. patches is an alias of the canonical patch key; both parse
patches configures files and directories to be mapped into the tasks’ runtime environment.
[tasks.my_task]
patches.dir."~/.claude" = "read-write"
patches.file."~/.claude.json" = "read-write"
patches is a structure with two optional fields, dir & file, each of which contains a mapping
of file paths to be mapped in, and the corresponding map mode. The map mode may only be the
string “read-only” / “ro” for read-only mappings, or the string “read-write” / “rw” for writeable
mappings.
If a mapped file or directory does not exist on the host, an empty file or directory is created.
Mapped paths must be absolute or start with ~/, in which case the tilde is expanded to the user’s
home directory.
inherit_cwd - Use parent working directory instead of repository root
Optional, Default false
inherit_cwd configures minimal to setup the task in the current working directory, instead
of the repository root.
[tasks.my_task]
inherit_cwd = true
args - Pass arguments to tasks
Optional
args configures named arguments and their datatype, which can be substituted into the command
being executed by the task.
[tasks.greeter]
args.name = "string"
args.greeting = "string"
exec = "echo %{greeting} %{name}"
Arguments without a default become mandatory for invoking the task. In the example above, running
the task greeter without its two arguments will trigger an error:
$> mip run greeter
error: the following required arguments were not provided:
--name <name>
--greeting <greeting>
Usage: mip run greeter --name <name> --greeting <greeting>
Each argument’s datatype may be:
- a scalar:
"string","number", or"boolean"(alias"bool"); - an array of a scalar type:
"Array string","Array number","Array boolean"; - an enum of permitted values, written either as the string
"[a, b]"or as a TOML array["a", "b"].
Instead of a bare datatype string, an argument can be declared as a table with a
type field plus optional help (a human-readable description) and default
(making the argument optional):
[tasks.greeter]
args.name = { type = "string", help = "who to greet", default = "world" }
exec = "echo Hello %{name}"