You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Now that the ENV_PRECEDENCE experiment is stable, it's about time we do a review of variables and environment variables and their behavior in Task. There are countless issues related to variables in v3 and we're going to be breaking them in the next major release anyway. This is the perfect time to have a think about whether there is anything else that we want to change.
This thread is a rollup issue (megathread, epic, whatever you want to call it) to discuss all variable-related ideas at a high level (no implementation details) so that we can discuss them together and in the context of a breaking change. Individual changes will be linked as child issues.
Here is a (non-exhaustive) list of the discussions that have been had elsewhere
Basically, they have a special file that key=value information can be written to, which then becomes a map that can be accessed in subsequent steps. For Task, I imagine this could be done via a single global variable or more granular, by introducing a name: or id: attribute for individual cmds.
On the topic of GitHub Actions-type functionality, I also want to mention conditional task execution based on variables, though this can already be accomplished by using templating in the task name which if it's empty, no task will run.
There are also two issues I want to mention here, both of which are about variable evaluation order becoming unexpectedly non-deterministic in some circumstances: #2058 (with included taskfiles) and #1847 (with .env files). Based on the other issues linked here, I suspect this will probably be addressed, but I still wanted to bring it up directly.
Edit: Passing information from a previous command combined with conditionally running a subsequent one (say with a command-level if: attribute) would be incredibly useful, in my opinion. I also too often up with small scripts in |- blocks because passing information isn't possible between steps.
Here's what ChatGPT had to say when I asked it about the vars problems:
Thought for 9 seconds
In digging through the go-task repo’s issue tracker, a few recurring themes pop up around how Task handles its vars vs. env values and the order in which they’re applied:
By default, go-task lets lower-level definitions override higher-level ones (“bottom-up”), so an environment variable you export on the shell can silently overwrite a vars: entry deeper in the Taskfile, and even two vars: entries will clobber each other depending on include order ([GitHub]1).
In contrast, classic tools like Make and Ant enforce immutable, top-down precedence: the first assignment “wins,” and later attempts to reassign are either ignored or error out.
Task’s separate vars: vs. env: namespaces, combined with their mutable resolution, leads to confusion: sometimes you expect env to override, other times you don’t ([GitHub]1, [GitHub]2).
During Taskfile parsing, collect all vars:andenv: declarations in the order they appear.
Once set, never allow a later declaration—whether from an include, a deeper task, or the user’s shell—to overwrite an existing value.
Explicit Override Only via CLI Flags
Mirror other build systems: let command-line flags (e.g. task --var FOO=bar) be the only way to override values, and require users to opt in.
Treat shell-exported ENV as read-only inputs rather than silent overrides, or provide a --ignore-env switch to disable them altogether.
Lazy (Deferred) Evaluation for Dynamic Needs
For cases where you do need “run-time” values (e.g. the output of a shell command), introduce a lazy: variable type (see Lazy global dynamic variable #1240) that only expands at execution time—without allowing it to clobber your statically declared constants ([GitHub]3).
Scoping & Namespacing
Introduce per-task scopes or namespaces so that variables local to a task can’t leak or be mutated globally (see work-in-progress Taskfile Scoped Variables #1030). This avoids accidental cross-task pollution.
Fail Fast on Reassign
If someone does try to redefine a vars: key a second time, Task should error out rather than silently honor the newer value. That immediately surfaces conflicts.
Why This Helps
Determinism: Every run sees the same values at each step—no more “it worked locally but failed in CI.”
Reproducibility: Remote caches, parallel execution, and includes all become safe once nobody can sneak in a late-bound override.
Easier Debugging: When a variable is immutable, you trace its origin to exactly one line in one file.
User Expectation Alignment: Go-task users coming from Make/Ant/Gradle will find it familiar: “first definition wins, and you know where that was.”
Implementing these changes would bring go-task’s variables into line with the immutability best practices used by established build tools—and save the maintainers (and end users) from an endless stream of “why did my VAR change?” tickets.
Reacted by Gerard Webb, Jonathan Backhaus, Łukasz Wencel and Evans Jahja
Reacted by Giovanni Tirloni, Ryan White, Ovidiu BOGDAN, sheldonhull, Iron-E and Rich Schumacher
Here's what ChatGPT had to say when I asked it about the vars problems:
Thought for 9 seconds
In digging through the go-task repo’s issue tracker, a few recurring themes pop up around how Task handles its vars vs. env values and the order in which they’re applied:
By default, go-task lets lower-level definitions override higher-level ones (“bottom-up”), so an environment variable you export on the shell can silently overwrite a vars: entry deeper in the Taskfile, and even two vars: entries will clobber each other depending on include order ([GitHub]1).
In contrast, classic tools like Make and Ant enforce immutable, top-down precedence: the first assignment “wins,” and later attempts to reassign are either ignored or error out.
Task’s separate vars: vs. env: namespaces, combined with their mutable resolution, leads to confusion: sometimes you expect env to override, other times you don’t ([GitHub]1, [GitHub]2).
During Taskfile parsing, collect all vars:andenv: declarations in the order they appear.
Once set, never allow a later declaration—whether from an include, a deeper task, or the user’s shell—to overwrite an existing value.
Explicit Override Only via CLI Flags
Mirror other build systems: let command-line flags (e.g. task --var FOO=bar) be the only way to override values, and require users to opt in.
Treat shell-exported ENV as read-only inputs rather than silent overrides, or provide a --ignore-env switch to disable them altogether.
Lazy (Deferred) Evaluation for Dynamic Needs
For cases where you do need “run-time” values (e.g. the output of a shell command), introduce a lazy: variable type (see Lazy global dynamic variable #1240) that only expands at execution time—without allowing it to clobber your statically declared constants ([GitHub]3).
Scoping & Namespacing
Introduce per-task scopes or namespaces so that variables local to a task can’t leak or be mutated globally (see work-in-progress Taskfile Scoped Variables #1030). This avoids accidental cross-task pollution.
Fail Fast on Reassign
If someone does try to redefine a vars: key a second time, Task should error out rather than silently honor the newer value. That immediately surfaces conflicts.
Why This Helps
Determinism: Every run sees the same values at each step—no more “it worked locally but failed in CI.”
Reproducibility: Remote caches, parallel execution, and includes all become safe once nobody can sneak in a late-bound override.
Easier Debugging: When a variable is immutable, you trace its origin to exactly one line in one file.
User Expectation Alignment: Go-task users coming from Make/Ant/Gradle will find it familiar: “first definition wins, and you know where that was.”
Implementing these changes would bring go-task’s variables into line with the immutability best practices used by established build tools—and save the maintainers (and end users) from an endless stream of “why did my VAR change?” tickets.
I got to admit, it took me a fair bit of time to understand how variables and envs work then using many task file includes to do overrides etc. I kind of like the idea of simple determinism and no changes at runtime.
Curious why this got a Down vote. Just want to understand the perspectives...
@pd93@andreynering Regarding vars and envars, are you interested in suggestions for non-breaking changes to address this area in Task? I could share a unified approach, here or in a linked issue, that would work using v3 schema techniques.
@solvingj There is nothing further to share on this topic at this time. As soon as there is, rest assured it will be posted here. Please understand that we are volunteers working on free software with many open issues and feature requests. We all have full time jobs and limited time to work on this project and other things have taken priority recently.
I'm still very open to improvements in this area, but we need constructive comments from the community on how to proceed. Some of the comments above that express opinions or share related information are really helpful and I encourage more of these. Anyone who has thoughts on this topic is welcome to post them here and I promise that they will all be read and considered by the maintainers. However, we cannot make any promises about timelines.
Reacted by Jerry Wiltse
Reacted by Nicholas Hairs, Steve Stuart and Zlatko Bratkovic
@pd93 Thanks for the response and contributions. Not asking for promises on timelines.
we need constructive comments from the community
Here's mine. For big breaking change like this, projects can often get paralyzed by community disagreement. However, in this case, I've read through all the comments and threads and think you are quite fortunate in this regard. While there's some nuance, the community seems quite unanimous on a few perspectives:
The existing behavior wasn't clearly documented behavior to be relied on
The existing behavior is dubious, unintuitive, and it's reasonable to describe it as a bug
Lots of prior art to suggest Immutable, deterministic ordering for the core behavior
You have an active community willing to test candidates and provide feedback
Also, the variable behavior (and implementation) is so fundamental it likely has many unforeseeable consequences, perhaps this is also paralyzing. While the notion: "If we're breaking something lets break it all and get it right once", makes sense, tactically speaking I recommend starting a public beta branch. There, begin with just one change, the most fundamental high priority one, and then iterate from there based on feedback (one change at a time).
Perhaps the most fundamental problem to start with is: "shell can take precedence over vars/env", but it's up to you where to start.
I'd like to vote for adding this issue to the list: #2079
I would very much like the ability to prompt for missing vars. Currently I have to rely on gum, which may or may not be installed. Would be much more portable if that was just built in to task.
I'd like to vote for adding this issue to the list: #2079
I would very much like the ability to prompt for missing vars. Currently I have to rely on gum, which may or may not be installed. Would be much more portable if that was just built in to task.
@pd93 I've put a lot more miles on go-task in the past 3 months, so I think I have some more concrete feedback on this topic. A detail of our context is cross-platform, everything we do needs to work "the same" on windows/mac/linux. We do most things on mac/linux, and later they have to run on windows.
Template vs Env syntax in cmds
We've gone back and forth several times on whether or not to maintain a convention that all cmds ONLY use env vars and DONT use template syntax.
We did a ton of the initial work just using template syntax in our cmds.
It means we can't easily lift and shift stuff out to separate scripts when we want, but thats rare anyway
But then we found that template syntax doesn't work the same as env vars for windows paths with backslashes
Converting to env and using $mypath preserves the backslashes, while raw template syntax loses it
We set a policy "only forwardslashes on paths everywhere, even windows", but it's not enforceable, and doesn't work with registry key syntax.
Cmds vs Status and Preconditions
Env vars aren't available at status and preconditions scopes
This means we are forced to use template syntax in these scopes
So, if/when we need to use backslashes in these, it's super awkward
We use env var syntax and regular backslashes in the commands, but status needs template syntax with quad backquotes
I understand why variable scopes at Status and Precondition times might be fundamentally different from cmds. However, the nuance that this leads to inconsistent syntax availability between these scopes is maybe a nuanced problem nobody's captured yet.
For anyone in this thread who'd prefer the opposite design call, I've been building a hard fork that inverts it: rite.
rite's variable precedence is first-in-wins, and shell env always takes priority over Ritefile env: — matching Unix conventions. Task-scope vars: are defaults, not overrides. SPEC is explicit about the seven-tier precedence model.
This isn't a critique of go-task's design choice. Upstream has publicly committed to preserving the current semantics, and for many users that model works fine. rite simply exists for users who want the other choice, cleanly.
Try it:
Install: brew install clintmod/tap/rite (or go install github.com/clintmod/rite/cmd/rite@v1.0.1)
Migrate from a Taskfile: rite --migrate Taskfile.yml — auto-rewrites to a Ritefile.yml and emits warnings at every site where semantics change (OVERRIDE-VAR, OVERRIDE-ENV, DOTENV-ENTRY, SECRET-VAR, TEMPLATE-KEPT).
Coexistence — rite and go-task can be installed side-by-side; they don't touch each other's state directories.
Thanks to the go-task maintainers for the foundation we forked from — rite inherits their git history and MIT license, and we cherry-pick upstream security/CI work where it's orthogonal to the variable model.
Activity
varsandenv#2036WinkelCode commentedon Feb 11, 2025
Something that would be very useful to me personally would be ways to pass information out of a task and/or command. For example, I really like the GitHub Actions approach to this: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs
Basically, they have a special file that key=value information can be written to, which then becomes a map that can be accessed in subsequent steps. For Task, I imagine this could be done via a single global variable or more granular, by introducing a
name:orid:attribute for individual cmds.On the topic of GitHub Actions-type functionality, I also want to mention conditional task execution based on variables, though this can already be accomplished by using templating in the task name which if it's empty, no task will run.
There are also two issues I want to mention here, both of which are about variable evaluation order becoming unexpectedly non-deterministic in some circumstances: #2058 (with included taskfiles) and #1847 (with .env files). Based on the other issues linked here, I suspect this will probably be addressed, but I still wanted to bring it up directly.
Edit: Passing information from a previous command combined with conditionally running a subsequent one (say with a command-level
if:attribute) would be incredibly useful, in my opinion. I also too often up with small scripts in|-blocks because passing information isn't possible between steps.clintmod commentedon May 8, 2025
Here's what ChatGPT had to say when I asked it about the vars problems:
Thought for 9 seconds
In digging through the go-task repo’s issue tracker, a few recurring themes pop up around how Task handles its
varsvs.envvalues and the order in which they’re applied:Backwards Precedence (Issue Environment variables from shell takes precedence over task file definition #1038)
vars:entry deeper in the Taskfile, and even twovars:entries will clobber each other depending on include order ([GitHub]1).Unexpected Mixing of
envandvars(Issue Environment variables from shell takes precedence over task file definition #1038 & Environment variables wrongly lead the precedence #2162)vars:vs.env:namespaces, combined with their mutable resolution, leads to confusion: sometimes you expectenvto override, other times you don’t ([GitHub]1, [GitHub]2).Recommended Solution: Adopt Immutable, Top-Down Variable Semantics
One-Pass, Write-Once Resolution
vars:andenv:declarations in the order they appear.Explicit Override Only via CLI Flags
task --var FOO=bar) be the only way to override values, and require users to opt in.--ignore-envswitch to disable them altogether.Lazy (Deferred) Evaluation for Dynamic Needs
lazy:variable type (see Lazy global dynamic variable #1240) that only expands at execution time—without allowing it to clobber your statically declared constants ([GitHub]3).Scoping & Namespacing
Fail Fast on Reassign
vars:key a second time, Task should error out rather than silently honor the newer value. That immediately surfaces conflicts.Why This Helps
Implementing these changes would bring go-task’s variables into line with the immutability best practices used by established build tools—and save the maintainers (and end users) from an endless stream of “why did my VAR change?” tickets.
joeblew999 commentedon Jun 2, 2025
I got to admit, it took me a fair bit of time to understand how variables and envs work then using many task file includes to do overrides etc. I kind of like the idea of simple determinism and no changes at runtime.
Curious why this got a Down vote. Just want to understand the perspectives...
trulede commentedon Jun 12, 2025
@pd93 @andreynering Regarding vars and envars, are you interested in suggestions for non-breaking changes to address this area in Task? I could share a unified approach, here or in a linked issue, that would work using v3 schema techniques.
solvingj commentedon Aug 30, 2025
No updates from maintainers in 6 months. Pinging @pd93 for any sort of status.
pd93 commentedon Aug 30, 2025
@solvingj There is nothing further to share on this topic at this time. As soon as there is, rest assured it will be posted here. Please understand that we are volunteers working on free software with many open issues and feature requests. We all have full time jobs and limited time to work on this project and other things have taken priority recently.
I'm still very open to improvements in this area, but we need constructive comments from the community on how to proceed. Some of the comments above that express opinions or share related information are really helpful and I encourage more of these. Anyone who has thoughts on this topic is welcome to post them here and I promise that they will all be read and considered by the maintainers. However, we cannot make any promises about timelines.
solvingj commentedon Sep 1, 2025
@pd93 Thanks for the response and contributions. Not asking for promises on timelines.
Here's mine. For big breaking change like this, projects can often get paralyzed by community disagreement. However, in this case, I've read through all the comments and threads and think you are quite fortunate in this regard. While there's some nuance, the community seems quite unanimous on a few perspectives:
Also, the variable behavior (and implementation) is so fundamental it likely has many unforeseeable consequences, perhaps this is also paralyzing. While the notion: "If we're breaking something lets break it all and get it right once", makes sense, tactically speaking I recommend starting a public beta branch. There, begin with just one change, the most fundamental high priority one, and then iterate from there based on feedback (one change at a time).
Perhaps the most fundamental problem to start with is: "shell can take precedence over vars/env", but it's up to you where to start.
Good luck and thanks for the contribution.
twelvelabs commentedon Sep 28, 2025
I'd like to vote for adding this issue to the list: #2079
I would very much like the ability to prompt for missing vars. Currently I have to rely on gum, which may or may not be installed. Would be much more portable if that was just built in to task.
solvingj commentedon Feb 4, 2026
@twelvelabs Good news, they merged "prompt for required variables" (via interactive flag) in 3.47.0:
https://github.com/go-task/task/releases/tag/v3.47.0
solvingj commentedon Feb 4, 2026
@pd93 I've put a lot more miles on go-task in the past 3 months, so I think I have some more concrete feedback on this topic. A detail of our context is cross-platform, everything we do needs to work "the same" on windows/mac/linux. We do most things on mac/linux, and later they have to run on windows.
Template vs Env syntax in cmds
cmds.Cmds vs Status and Preconditions
I understand why variable scopes at Status and Precondition times might be fundamentally different from cmds. However, the nuance that this leads to inconsistent syntax availability between these scopes is maybe a nuanced problem nobody's captured yet.
clintmod commentedon Apr 14, 2026
For anyone in this thread who'd prefer the opposite design call, I've been building a hard fork that inverts it: rite.
rite's variable precedence is first-in-wins, and shell env always takes priority over Ritefile
env:— matching Unix conventions. Task-scopevars:are defaults, not overrides. SPEC is explicit about the seven-tier precedence model.This isn't a critique of go-task's design choice. Upstream has publicly committed to preserving the current semantics, and for many users that model works fine. rite simply exists for users who want the other choice, cleanly.
Try it:
brew install clintmod/tap/rite(orgo install github.com/clintmod/rite/cmd/rite@v1.0.1)rite --migrate Taskfile.yml— auto-rewrites to aRitefile.ymland emits warnings at every site where semantics change (OVERRIDE-VAR, OVERRIDE-ENV, DOTENV-ENTRY, SECRET-VAR, TEMPLATE-KEPT).Read first:
Thanks to the go-task maintainers for the foundation we forked from — rite inherits their git history and MIT license, and we cherry-pick upstream security/CI work where it's orthogonal to the variable model.
Questions welcome at rite/discussions; bugs at rite/issues.