Environment

Sources & precedence

Sources are merged — never replaced. A task's env adds to and overrides the inherited set; it does not wipe PATH and friends. Highest wins:

TEXT
task env  >  task env_file(s)  >  workspace [env]  >  root .env file  >  process env
TOML
[env]
NODE_ENV = "development"

[tasks.ci]
env = { CI = "true" }        # adds CI, keeps everything else

.env loading

  • Only the workspace-root .env is auto-loaded (next to tasks.toml) — no flag.

  • Per-package .env files are not auto-loaded, by design: frameworks (Next, Vite, …) load their own app-level .env at runtime; tsr owns only the shared, workspace-level vars.

env_file — per-task files

A task can load its own .env-style files with env_file, as a single path or a list. This is how a task overrides the default .env — e.g. a test task that needs .env.test:

TOML
[tasks.test]
run = "vitest"
env_file = [".env.local", ".env.test"]   # .env.test overrides .env.local
  • Precedence: env_file values layer above the root .env and workspace [env], and below the inline task env.

  • Order: applied left to right — later files override earlier ones. A single string is a one-element list.

  • Paths are relative to the task's dir (or the workspace root when unset).

  • Missing files are skipped (like the root .env), so an optional .env.local need not exist — convenient in CI.

Expansion

  • $VAR / ${VAR} are expanded after the full merge, against the final resolved env.

  • [env] values may reference the process env and earlier keys in the same block. No forward references; there is no dependency graph for env resolution.

TOML
[env]
ROOT = "/srv/app"
LOGS = "$ROOT/logs"          # → /srv/app/logs
Undefined variables are a hard error

A $VAR referenced in a run string but defined nowhere (task env, env_file, workspace [env], or .env) is a config error, caught at load time, exit 64:



✗ config error: task 'deploy': '$TARGET' is not defined…

Process env

The process environment is fully inherited; there is no filtering or allow-listing in v1.