vscode: Environment variables defined in `tasks.json::tasks.options.env` do not overwrite existing environment variables

Code: 1.22.2 macOS: 10.13.3

When I create a task, I want to provide a very specific environment (which is why I bother filling out the JSON values). However, if an environment variable is pre-existing, it will not be overwritten in the execution environment of the task.

I am extremely surprised by this behavior. I discovered this by not being able to find a library I was building with a task when specifying DYLD_LIBRARY_PATH.

A very simple test is to update ~/.bash_profile to include

export FOO=bar

Then attempt to override it with tasks.json

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "diagnostic",
            "type": "shell",
            "options": {
                "cwd": "${workspaceFolder}/beam",
                "env": {
                    "FOO": "baz",
                }
            },
            "command": "printenv",
            "problemMatcher": []
        }
    ]
}

Output:

...
FOO=bar
...

A “task” appears to execute . ~/.bash_profile to set it’s environment. Since it is already creating a new environment for each execution, it would be nice if I could override the values as I see fit, by specifying the values in tasks.options.env.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 6
  • Comments: 51 (21 by maintainers)

Most upvoted comments

This issue and the handling of “env” options in tasks.json is kind of confusing. I am developing on Windows and Linux (and prototyping for the latter on WSL) and adding to what @zfields reported, I have created this tasks.json.

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "check (bash)",
            "type": "shell",
            "windows": {
                "options": {
                    "shell": {
                        "executable": "bash.exe",
                        "args": [ "-c" ]
                    }
                }
            },
            "linux" : {
                "options": {
                    "shell": {
                        "executable": "bash",
                        "args": [ "-c" ]
                    }
                }
            },
            "options": {
                "env": {
                    "BAR" : "BAZ"
                },
            },
            "command": "echo env{FOO}=${env:FOO} env{BAR}=${env:BAR} var{BAR}=$BAR",
            "problemMatcher": []
        },
        {
            "label": "check (cmd)",
            "type": "shell",
            "windows": {
                "options": {
                    "shell": {
                        "executable": "cmd.exe",
                        "args" : [ "/C" ]
                    }
                }
            },
            "options": {
                "env": {
                    "BAR" : "BAZ"
                }
            },
            "command": "echo env{FOO}=${env:FOO} env{BAR}=${env:BAR} var{BAR}=%BAR%",
            "problemMatcher": []
        }
    ]
}

Results (without FOO or BAR set/exported in the parent shell that executes code)

  1. Windows cmd.exe env{FOO}= env{BAR}= var{BAR}=BAZ
  2. WSL bash.exe env{FOO}= env{BAR}= var{BAR}=
  3. Linux/Mac OS bash env{FOO}= env{BAR}= var{BAR}=BAZ

Results (with set/exported FOO=foo BAR=bar in the parent shell that executes code)

  1. Windows cmd.exe env{FOO}=foo env{BAR}=bar var{BAR}=BAZ
  2. WSL bash.exe env{FOO}=foo env{BAR}=bar var{BAR}=
  3. Linux/Mac OS bash env{FOO}=foo env{BAR}=bar var{BAR}=BAZ

I am assuming that Mac OS behaves like Linux. Update 2019/02/13: MacOS behaves like Linux.

Tested with Visual Studio Code 1.31.

Bottom line is:

  • ${env:...} only expands environment variables that were set in the parent shell that ran code. It doesn’t expand variables set in the tasks.json env options.
  • %var% expands env variables from the tasks.json options when running cmd.exe. So does $var for real Linux bash. But on WSL, $var doesn’t expand env variables from the tasks.json options.
  • %var% (cmd.exe) and $var (Linux bash) may expand to values different from ${env:var} if var was set in code’s parent shell.

Is this a bug or by design??

FWIW, I would prefer if ${env:var} would also expand the env options of the tasks.json file, superseding everything that was set in the environment of code’s parent shell. If ${env:var} is the standard way to expand environment (and env options) variables, the task’s command may be unified across platforms because there’s no need for $var vs. %var%.

If not a bug, would that be a feature request?

Since you are defining the value in the tasks.json, you already know the expanded value and you could easily just include the expanded value in the task command. https://github.com/microsoft/vscode/issues/72323#issuecomment-487523367

This deserves reconsideration. Yes, I know the value that’s going to be expanded, but I’d like to not check this value into source code.

I’d really like the ability to run tasks with arguments passed from launch.json, but pre-defining my environment or arguments in tasks.json is a non-starter for me. I need a .env or local.settings.json or equivalent file that I can read from and .gitignore.

NOT ok to close.

How is VSCode setting the environment variables I request in tasks.options.env?

What happens when you open an integrated terminal and execute printenv in it. Does it source things from the profile as well.

Yes.

Sorry, we are in end game. If you want to inject something into the shell the best is to build a command that does both. For example ‘set X=“abc” & myCommand’.

What is the point of specifying tasks.options.env, if putting everything in the command string is the only reliable way to ensure the environment variables are getting set?

It seems like I have identified a bug for you, and you have identified a solution. However, you are suggesting that I hack it in place, instead of VSCode correctly assembling the command string by prepending the setting of environment variables to the specified command.

To use your example, VSCode is taking a command string from me, and also has all the environment variables I wish to have set.

{
...
  "options": {
    "env": {
      "X": "abc"
    }
  },
  "command": "myCommand",
...
}

Why isn’t VSCode capable of constructing the following string on my behalf, before passing myCommand to its internal command line API?

set X="abc" && myCommand

Note the “double ampersand” to ensure they run synchronously and complete successfully

Now that we are on the same page with the behavior, shouldn’t this be marked a bug - instead of a feature request?

I specify environment variables in my task, but I do not see them in the environment in which VSCode executes the task.

In other words, the behavior does not meet reasonable expectations, and is impacting my workflow (smells like a bug). The main reason I am pushing for the distinction is because I know “bugs” get prioritized and fixed while “feature requests” get punted.

To provide some context, I work at a cross platform shop, and I’m trying to get them to switch over to VSCode instead of using native IDEs for each platform. As you know, people resist change and things like this make it easy for people to pick apart, even when it is an awesome product. Please help me get this fixed.

Just to confirm…

  1. I cold booted my Mac

  2. Opened VSCode to a new folder on the system

  3. Added a new task “diagnostic”, based on the “Others” template

    {
        // See https://go.microsoft.com/fwlink/?LinkId=733558
        // for the documentation about the tasks.json format
        "version": "2.0.0",
        "tasks": [
            {
                "label": "diagnostic",
                "type": "shell",
                "command": "printenv"
            }
        ]
    }
    
  4. I execute the task from the “Tasks >> Run Task…” menu

screen shot 2018-04-22 at 10 52 37 pm
  1. In the output appearing in the “TERMINAL” tab of VSCode UI, I can see environment variables only declared in ~/.bash_profile
screen shot 2018-04-22 at 10 50 09 pm

How is this happening, if executing a task from the UI in VSCode is not loading environment varialbes from ~/.bash_profile?

My expectation is as follows. If I am defining environment variables in tasks.options.env, then I would expect those environment variables to be available to the task, regardless. In other words, tasks.options.env should override everything else.

Specifically, DYLD_LIBRARY_PATH is very important to me. Several of my workflows require my company’s release libraries (i.e. export DYLD_LIBRARY_PATH=/Users/zfields/lib set in ~/.bash*). However, when I’m in a development environment I will need to override the release libraries with debug versions .../debug/lib so I can step through and debug.

It would be great if I could simply override DYLD_LIBRARY_PATH in the task, so I can develop and debug without modifying my file system.

Sorry for the short answer. Normally bash defines that ~/.bash_profile should only be considered when the shell / terminal is a login shell (for example when you login over ssh into a machine). However the standard Mac Terminal application doesn’t stick to this rule and considers the content of ~/.bash_profile for all started shell (hence the behavior you are seeing). When I retested this today I used the standard Mac Terminal as was able to reproduce the behavior you are seeing.

No, I wasn’t aware the setting existed, so any behavior is the default behavior of VSCode. Following on, does it stand to reason that VSCode does in fact consume ~/.bash_profile?

If so, are you able to inject the environment variables I have supplied in tasks.options.env, AFTER it consumes ~/.bash_profile? If you can, it would truly be helpful when constructing an environment in which to execute a task (the crux of the issue at hand).

I can confirm this works as expected now, and the point of this original issue has been resolved to my satisfaction.

Thank you!

I will reactive my Mac and will have a look beginning of next week.

@jwmurray Please open a separate issue for your experience, as it is not directly related to this issue. This issue is still open / under investigation, and it took forever for the bug listed above to be recognized and confirmed by Microsoft. I’m sorry to shut you down, but there has been enough confusion on this thread.

@dbaeumer It has been several months since you were going to test again on your Mac, and I am curious if you have been able to land on a solution.

Should this work the same on Windows?

I have the following task:

{
            "label": "test"
            ,"type": "shell"
            ,"options": {
                "env": {
                    "message": "hello"
                }
            }
            
            ,"windows": {
                
                "command": "echo $message"
            }
            ,"group": {
                "kind": "build",
                "isDefault": true
            }
            ,"presentation": {
                "panel": "new",
            }
}

and get the following output:

> Executing task: echo $message <


Press any key to close the terminal.

Not sure why $message is not expanded to the environment variable that is being set.

@zfields thanks a lot for testing this. I will look into why some of the behavior is unexpected.

Regarding 2.) this is expected in the sense that things from ~/.bashrc should not influence shells started with -c (which tasks do). If you want a different behavior you need to remove

case $- in
    *i*) ;;
      *) return;;
esac

from the beginning however then exports from the ~/.bashrc will always win.

Can you let me know in terms of variables which once you want to define were and which once should win?

I see your point. On the other hand we need to work around a bug caused by another tool 😃.

I am still not sure if we should try to fix this on our end since it will for sure introduce another set of problems (command line length if a lot of environment variables are set, …). When reading about it people advised the following trick:

  • have a ~/.bashrc with this at the beginning
# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac
  • in your profile have
if [ -f ~/.bashrc ]; then
   source ~/.bashrc
fi
  • set the variable in the ~/.bashrc file
export FOO=bar

I haven’t tested this but I think it should work around the problem for now.

Yes.

This is really strange since values from the profile should only be sourced when --login is specified and the rc files usually have interactive check at the beginning. Have you configured anything special on our machine.

We don’t inject something into the shell to keep it consistent with the process type were we can’t do such tricks and rely on what the process sees when executed.

I will keep the item open to consider it as a feature request to treat this different for shells

I tried this on Apple and Linux and in both cases the ~/.bash_profile was not source for me unless I used a login shell. This is why I ask you what happens for you when you run bash from a different shell or even form a terminal already running bash without --login. Will the code from ~/.bash_profile be executed.

I think we are talking past each other, maybe we should take two steps back…

I have no desire to use a shell. In fact, I ONLY want to use VSCode.

However, when running a “shell task” in VSCode (i.e. diagnostic from above), I happened to notice VSCode picks up environment variables that are only set in my ~/.bash_profile. If you are not able to reproduce this on your non-Apple device, then perhaps it IS platform specific.

I think we need to make sure we are experiencing the same behavior, before we can attempt to solve this problem.