ansible-zuul[bot]: Build failed.

- [ansible-tox-py36 ](https://logs.zuul.ansible.com/15/315/805ce3455baf9663d19bc0ec7b85e688b9369789/check/ansible-tox-py36/1e77548/) : SUCCESS in 7m 52s
- [ansible-tox-py37 ](https://logs.zuul.ansible.com/15/315/805ce3455baf9663d19bc0ec7b85e688b9369789/check/ansible-tox-py37/35a4067/) : SUCCESS in 7m 55s
- [ansible-build-python-tarball ](https://logs.zuul.ansible.com/15/315/805ce3455baf9663d19bc0ec7b85e688b9369789/check/ansible-build-python-tarball/132a56d/) : SUCCESS in 2m 37s
- [ansible-tox-linters ](https://logs.zuul.ansible.com/15/315/805ce3455baf9663d19bc0ec7b85e688b9369789/check/ansible-tox-linters/2788102/) : SUCCESS in 4m 50s
- [ansible-tox-py27 ](https://logs.zuul.ansible.com/15/315/805ce3455baf9663d19bc0ec7b85e688b9369789/check/ansible-tox-py27/dc98d32/) : FAILURE in 6m 56s

ansible-zuul[bot]: Build failed.

- [ansible-tox-py36 ](https://logs.zuul.ansible.com/15/315/af88addad082828a67932ea2f5e05c0f1043cf55/check/ansible-tox-py36/b45e3de/) : SUCCESS in 7m 35s
- [ansible-tox-py37 ](https://logs.zuul.ansible.com/15/315/af88addad082828a67932ea2f5e05c0f1043cf55/check/ansible-tox-py37/82fbe5f/) : SUCCESS in 7m 39s
- [ansible-build-python-tarball ](https://logs.zuul.ansible.com/15/315/af88addad082828a67932ea2f5e05c0f1043cf55/check/ansible-build-python-tarball/db937bb/) : SUCCESS in 2m 37s
- [ansible-tox-linters ](https://logs.zuul.ansible.com/15/315/af88addad082828a67932ea2f5e05c0f1043cf55/check/ansible-tox-linters/b365b7f/) : SUCCESS in 4m 57s
- [ansible-tox-py27 ](https://logs.zuul.ansible.com/15/315/af88addad082828a67932ea2f5e05c0f1043cf55/check/ansible-tox-py27/3cdf276/) : FAILURE in 6m 56s

matburt: recheck
ansible-zuul[bot]: Build failed.

- [ansible-tox-py36 ](https://logs.zuul.ansible.com/15/315/9f3d1f71ff321aa88d482c5330ed98a8ff49df5f/check/ansible-tox-py36/b61f96a/) : FAILURE in 8m 11s
- [ansible-tox-py37 ](https://logs.zuul.ansible.com/15/315/9f3d1f71ff321aa88d482c5330ed98a8ff49df5f/check/ansible-tox-py37/1e58bda/) : FAILURE in 7m 56s
- [ansible-build-python-tarball ](https://logs.zuul.ansible.com/15/315/9f3d1f71ff321aa88d482c5330ed98a8ff49df5f/check/ansible-build-python-tarball/cd2b118/) : SUCCESS in 3m 00s
- [ansible-tox-linters ](https://logs.zuul.ansible.com/15/315/9f3d1f71ff321aa88d482c5330ed98a8ff49df5f/check/ansible-tox-linters/0cedd54/) : SUCCESS in 5m 04s
- [ansible-tox-py27 ](https://logs.zuul.ansible.com/15/315/9f3d1f71ff321aa88d482c5330ed98a8ff49df5f/check/ansible-tox-py27/bdadcb6/) : FAILURE in 7m 24s

matburt: recheck
matburt: recheck
ansible-zuul[bot]: Build succeeded.

- [ansible-tox-py36 ](https://logs.zuul.ansible.com/15/315/7dd28b19aef3633e216222c7bdbcf92927ed65af/check/ansible-tox-py36/51a9d89/) : SUCCESS in 13m 37s
- [ansible-tox-py37 ](https://logs.zuul.ansible.com/15/315/7dd28b19aef3633e216222c7bdbcf92927ed65af/check/ansible-tox-py37/6c3aed3/) : SUCCESS in 15m 42s
- [ansible-build-python-tarball ](https://logs.zuul.ansible.com/15/315/7dd28b19aef3633e216222c7bdbcf92927ed65af/check/ansible-build-python-tarball/c92a50a/) : SUCCESS in 2m 31s
- [ansible-tox-linters ](https://logs.zuul.ansible.com/15/315/7dd28b19aef3633e216222c7bdbcf92927ed65af/check/ansible-tox-linters/3ac1847/) : SUCCESS in 4m 51s
- [ansible-tox-py27 ](https://logs.zuul.ansible.com/15/315/7dd28b19aef3633e216222c7bdbcf92927ed65af/check/ansible-tox-py27/11ae112/) : SUCCESS in 15m 09s

diff --git a/ansible_runner/loader.py b/ansible_runner/loader.py
index 7e358e7..55f9f69 100644
--- a/ansible_runner/loader.py
+++ b/ansible_runner/loader.py
@@ -18,6 +18,7 @@
 #
 import os
 import json
+import codecs
 
 from yaml import safe_load, YAMLError
 from six import string_types
@@ -95,8 +96,7 @@ class ArtifactLoader(object):
         try:
             if not os.path.exists(path):
                 raise ConfigurationError('specified path does not exist %s' % path)
-
-            with open(path) as f:
+            with codecs.open(path, encoding='utf-8') as f:
                 data = f.read()
 
             return data
diff --git a/ansible_runner/runner.py b/ansible_runner/runner.py
index c9d4a89..3b31dd8 100644
--- a/ansible_runner/runner.py
+++ b/ansible_runner/runner.py
@@ -103,7 +103,10 @@ class Runner(object):
                 raise
         os.close(os.open(stdout_filename, os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR))
 
-        command = [a.decode('utf-8') if six.PY2 else a for a in self.config.command]
+        if six.PY2:
+            command = [a.decode('utf-8') for a in self.config.command]
+        else:
+            command = self.config.command
         with codecs.open(command_filename, 'w', encoding='utf-8') as f:
             os.chmod(command_filename, stat.S_IRUSR | stat.S_IWUSR)
             json.dump(
diff --git a/ansible_runner/runner_config.py b/ansible_runner/runner_config.py
index 4c0d7bf..b333a2e 100644
--- a/ansible_runner/runner_config.py
+++ b/ansible_runner/runner_config.py
@@ -220,18 +220,20 @@ class RunnerConfig(object):
         self.expect_passwords[pexpect.TIMEOUT] = None
         self.expect_passwords[pexpect.EOF] = None
 
+        # seed env with existing shell env
+        self.env = os.environ.copy()
+        if self.envvars and isinstance(self.envvars, dict):
+            if six.PY2:
+                self.env.update({k.decode('utf-8'):v.decode('utf-8') for k, v in self.envvars.items()})
+            else:
+                self.env.update({k:v for k, v in self.envvars.items()})
         try:
-            # seed env with existing shell env
-            self.env = os.environ.copy()
             envvars = self.loader.load_file('env/envvars', Mapping)
             if envvars:
-                self.env.update({k:six.text_type(v) for k, v in envvars.items()})
-            if self.envvars and isinstance(self.envvars, dict):
-                self.env.update({k:six.text_type(v) for k, v in self.envvars.items()})
+                self.env.update({six.text_type(k):six.text_type(v) for k, v in envvars.items()})
         except ConfigurationError:
             output.debug("Not loading environment vars")
             # Still need to pass default environment to pexpect
-            self.env = os.environ.copy()
 
         try:
             self.settings = self.loader.load_file('env/settings', Mapping)

Unicode in environment variables break ansible-runner 
We run ansible-runner in Docker containers in Gitlab CI.
ansible-runner breaks when a user with Umlauts in his name starts the runner, as the user's name (eg. `Müller`) is saved as environment variable.

```console
[root@af4df578c2e7 /]# unset test
[root@af4df578c2e7 /]# ansible-runner -p test.yml run .
ERROR! the playbook: test.yml could not be found
[root@af4df578c2e7 /]# export test=Müller
[root@af4df578c2e7 /]# ansible-runner -p test.yml run .
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/ansible_runner/__main__.py", line 329, in main
    res = run(**run_options)
  File "/usr/lib/python2.7/site-packages/ansible_runner/interface.py", line 162, in run
    r.run()
  File "/usr/lib/python2.7/site-packages/ansible_runner/runner.py", line 112, in run
    'env': self.config.env}, f, ensure_ascii=False
  File "/usr/lib64/python2.7/json/__init__.py", line 190, in dump
    fp.write(chunk)
  File "/usr/lib64/python2.7/codecs.py", line 691, in write
    return self.writer.write(data)
  File "/usr/lib64/python2.7/codecs.py", line 351, in write
    data, consumed = self.encode(object, self.errors)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 2: ordinal not in range(128)
[root@af4df578c2e7 /]# ansible-runner --version
1.3.4
```


## related issues

similar to #234

## link to breaking source

https://github.com/ansible/ansible-runner/blob/1696ec2075f96b57bc9099cf018ed8d0d574aed6/ansible_runner/runner.py#L109-L113
diff --git a/test/integration/test_main.py b/test/integration/test_main.py
index 8e3dff7..8416c85 100644
--- a/test/integration/test_main.py
+++ b/test/integration/test_main.py
@@ -1,7 +1,9 @@
+# -*- coding: utf-8 -*-
 from __future__ import print_function
 from ansible_runner.__main__ import main
 
 import os
+import codecs
 import multiprocessing
 import shutil
 import yaml
@@ -207,15 +209,15 @@ def test_role_run_artifacts_dir_abs():
 @pytest.mark.parametrize('envvars', [
     {'msg': 'hi'},
     {
-        'msg': b'\xf0\x98\x90\x9d\xe5\x83\xac\xe2\xb2\x82\xeb\x8d\xb6'.decode('utf-8'),
-        b'\xf0\x98\x90\x9d\xe5\x83\xac\xe2\xb2\x82\xeb\x8d\xb6'.decode('utf-8'): b'\xf0\x98\x90\x9d\xe5\x83\xac\xe2\xb2\x82\xeb\x8d\xb6'.decode('utf-8')
+        'msg': u'utf-8-䉪ቒ칸ⱷ?噂폄蔆㪗輥',
+        u'蔆㪗輥': u'䉪ቒ칸'
     }
 ])
 def test_role_run_env_vars(envvars):
 
     with temp_directory() as temp_dir:
         ensure_directory(os.path.join(temp_dir, 'env'))
-        with open(os.path.join(temp_dir, 'env/envvars'), 'w') as f:
+        with codecs.open(os.path.join(temp_dir, 'env/envvars'), 'w', encoding='utf-8') as f:
             f.write(yaml.dump(envvars))
 
         rc = main(['-r', 'benthomasson.hello_role',
diff --git a/test/integration/test_runner.py b/test/integration/test_runner.py
index 677b1ed..128cea1 100644
--- a/test/integration/test_runner.py
+++ b/test/integration/test_runner.py
@@ -42,6 +42,8 @@ def test_run_command_with_unicode(rc):
     if six.PY2:
         expected = expected.decode('utf-8')
     rc.command = ['echo', '"utf-8-䉪ቒ칸ⱷ?噂폄蔆㪗輥"']
+    rc.envvars = {"䉪ቒ칸": "蔆㪗輥"}
+    rc.prepare_env()
     status, exitcode = Runner(config=rc).run()
     assert status == 'successful'
     assert exitcode == 0
@@ -50,6 +52,7 @@ def test_run_command_with_unicode(rc):
         assert data.get('command') == ['echo', expected]
         assert 'cwd' in data
         assert isinstance(data.get('env'), dict)
+        assert u"䉪ቒ칸" in data.get('env')
 
 
 def test_run_command_finished_callback(rc):
diff --git a/test/unit/test_loader.py b/test/unit/test_loader.py
index ad23150..01aaaa2 100644
--- a/test/unit/test_loader.py
+++ b/test/unit/test_loader.py
@@ -112,7 +112,7 @@ def test_load_file_type_check(loader):
 
 
 def test_get_contents_ok(loader):
-    with patch('ansible_runner.loader.open') as mock_open:
+    with patch('codecs.open') as mock_open:
         handler = BytesIO()
         handler.write(b"test string")
         handler.seek(0)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest mock
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
-e git+https://github.com/ansible/ansible-runner.git@b26d57680812e81307455a76df9b4e979f933fd6#egg=ansible_runner
exceptiongroup==1.2.2
iniconfig==2.1.0
lockfile==0.12.2
mock==5.2.0
packaging==24.2
pexpect==4.9.0
pluggy==1.5.0
psutil==7.0.0
ptyprocess==0.7.0
pytest==8.3.5
python-daemon==3.1.2
PyYAML==6.0.2
six==1.17.0
tomli==2.2.1

name: ansible-runner
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - lockfile==0.12.2
      - mock==5.2.0
      - packaging==24.2
      - pexpect==4.9.0
      - pluggy==1.5.0
      - psutil==7.0.0
      - ptyprocess==0.7.0
      - pytest==8.3.5
      - python-daemon==3.1.2
      - pyyaml==6.0.2
      - six==1.17.0
      - tomli==2.2.1
prefix: /opt/conda/envs/ansible-runner

test/integration/test_runner.py::test_run_command_with_unicode test/unit/test_loader.py::test_get_contents_ok
test/integration/test_main.py::test_module_run test/integration/test_main.py::test_module_run_debug test/integration/test_main.py::test_module_run_clean test/integration/test_main.py::test_role_run test/integration/test_main.py::test_role_run_abs test/integration/test_main.py::test_role_logfile test/integration/test_main.py::test_role_logfile_abs test/integration/test_main.py::test_role_run_clean test/integration/test_main.py::test_role_run_cmd_line_abs test/integration/test_main.py::test_role_run_artifacts_dir test/integration/test_main.py::test_role_run_artifacts_dir_abs test/integration/test_main.py::test_role_run_env_vars[envvars0] test/integration/test_main.py::test_role_run_env_vars[envvars1] test/integration/test_main.py::test_role_run_args test/integration/test_main.py::test_role_run_inventory test/integration/test_main.py::test_playbook_start test/integration/test_runner.py::test_run_command_ansible test/integration/test_runner.py::test_run_command_ansible_event_handler test/integration/test_runner.py::test_run_command_ansible_event_handler_failure test/integration/test_runner.py::test_run_command_ansible_rotate_artifacts test/integration/test_runner.py::test_get_fact_cache test/integration/test_runner.py::test_set_fact_cache test/integration/test_runner.py::test_set_extra_vars
test/integration/test_main.py::test_temp_directory test/integration/test_main.py::test_help test/integration/test_main.py::test_role_bad_project_dir test/integration/test_main.py::test_role_run_inventory_missing test/integration/test_main.py::test_role_start test/integration/test_runner.py::test_password_prompt test/integration/test_runner.py::test_run_command test/integration/test_runner.py::test_run_command_finished_callback test/integration/test_runner.py::test_run_command_explosive_finished_callback test/integration/test_runner.py::test_run_command_explosive_cancel_callback test/integration/test_runner.py::test_run_command_cancel_callback test/integration/test_runner.py::test_run_command_job_timeout test/integration/test_runner.py::test_run_command_idle_timeout test/integration/test_runner.py::test_run_command_failed test/integration/test_runner.py::test_executable_not_found test/integration/test_runner.py::test_run_command_long_running test/integration/test_runner.py::test_run_command_long_running_children test/integration/test_runner.py::test_run_command_events_missing test/integration/test_runner.py::test_run_command_stdout_missing test/integration/test_runner.py::test_run_command_no_stats test/unit/test_loader.py::test__load_json_success test/unit/test_loader.py::test__load_json_failure test/unit/test_loader.py::test__load_yaml_success test/unit/test_loader.py::test__load_yaml_failure test/unit/test_loader.py::test_abspath test/unit/test_loader.py::test_load_file_text test/unit/test_loader.py::test_load_file_json test/unit/test_loader.py::test_load_file_type_check test/unit/test_loader.py::test_get_contents_invalid_path test/unit/test_loader.py::test_get_contents_exception
ansible_runner/runner_config.py ansible_runner/loader.py ansible_runner/runner.py
ansible_runner/runner_config.py ansible_runner/loader.py ansible_runner/runner.py