I have open sourced some [Ansible roles](https://github.com/codeghar/ansible-roles/) I use for my personal projects. Everything was working well until one day running my playbook would always fail on the [ansible.posix.synchronize task](https://github.com/codeghar/ansible-roles/blob/da59150257340bd914d63086947a6bce3c82ff9a/websites/tasks/rhel.yml#L18). ``` TASK [websites : push example.com contents] ************************************************************************************************************************** [ERROR]: Task failed: 'Connection' object has no attribute '_new_stdin' Origin: /Users/aikchar/ansible-roles/websites/tasks/rhel.yml:18:3 16 become: yes 17 18 - name: "push {{ domain_name }} contents" ^ column 3 fatal: [example.com]: FAILED! => {"changed": false, "msg": "Task failed: 'Connection' object has no attribute '_new_stdin'"} ``` # The Fix The better fix is to remove older collections. Read on to learn why this worked for me. ``` $ mv /Users/aikchar/.ansible/collections/ansible_collections /Users/aikchar/.ansible/collections/old_ansible_collections ``` With that fix applied, ``` TASK [websites : push example.com contents] *************************************************************************************************************** changed: [example.com] ``` # Alternative Fix Set custom value of [_collections_path_](https://docs.ansible.com/projects/ansible/latest/reference_appendices/config.html#collections-paths) in ansible.cfg (/Users/aikchar/infra-as-code/ansible.cfg), ``` [defaults] collections_path=/usr/local/Cellar/ansible/13.1.0/libexec/lib/python3.14/site-packages ... ``` Notice I am using Homebrew to install Ansible. Your path may be different if you are using a different Python installation, e.g. using virtualenvs. The disadvantage of this fix is that everytime I upgrade Ansible, I have to modify this file. Another thing to keep in mind is that the value should not be put into quotes, whether single or double. It causes a very weird issue, ``` # Without quotes (as it should be) $ ansible-config dump | grep COLLECTIONS_PATHS COLLECTIONS_PATHS(/Users/aikchar/infra-as-code/ansible.cfg) = ['/usr/local/Cellar/ansible/13.1.0/libexec/lib/python3.14/site-packages'] ``` ``` # With quotes (DO NOT DO) $ ansible-config dump | grep COLLECTIONS_PATHS COLLECTIONS_PATHS(/Users/aikchar/infra-as-code/ansible.cfg) = ["/Users/aikchar/infra-as-code/'/usr/local/Cellar/ansible/13.1.0/libexec/lib/python3.14/site-packages'"] ``` # The Reason The default value of _collections_path_ is `{{ ANSIBLE_HOME ~ "/collections:/usr/share/ansible/collections" }}`. I had not set _ANSIBLE_HOME_ environment variable, in which case it's set to the user's home directory (/Users/aikchar/.ansible), as seen below, ``` $ ansible-config dump | grep COLLECTIONS_PATHS COLLECTIONS_PATHS(default) = ['/Users/aikchar/.ansible/collections', '/usr/share/ansible/collections'] ``` At some point in 2022 I had installed Ansible collection for synchronize in this default path. I don't remember when or how or why; it was just there. ``` $ ls -lA /Users/aikchar/.ansible/collections/ansible_collections/ansible/posix/plugins/action total 48 -rw-r--r-- 1 aikchar staff 0 Feb 10 2022 __init__.py -rw-r--r-- 1 aikchar staff 2658 Feb 10 2022 patch.py -rw-r--r-- 1 aikchar staff 20202 Feb 10 2022 synchronize.py ``` The culprit was right in front of me, the version was [from 2021](https://github.com/ansible-collections/ansible.posix/blob/a65807edc322ec403eb73e006aa9f6e2172026a3/plugins/action/synchronize.py). ``` $ grep -B5 -A5 new_stdin /Users/aikchar/.ansible/collections/ansible_collections/ansible/posix/plugins/action/synchronize.py # Delegate to localhost as the source of the rsync unless we've been # told (via delegate_to) that a different host is the source of the # rsync if not use_delegate and remote_transport: # Create a connection to localhost to run rsync on new_stdin = self._connection._new_stdin # Unlike port, there can be only one shell localhost_shell = None for host in C.LOCALHOST: localhost_vars = task_vars['hostvars'].get(host, {}) -- break else: localhost_executable = C.DEFAULT_EXECUTABLE self._play_context.executable = localhost_executable new_connection = connection_loader.get('local', self._play_context, new_stdin) self._connection = new_connection # Override _remote_is_local as an instance attribute specifically for the synchronize use case # ensuring we set local tmpdir correctly self._connection._remote_is_local = True self._override_module_replaced_vars(task_vars) ``` Since there was a matching collection in the default path, it was being used instead of the ones packaged in the Ansible installation, ``` $ ls -lA /usr/local/Cellar/ansible/13.1.0/libexec/lib/python3.14/site-packages/ansible_collections/ansible/posix/plugins/action total 56 -rw-r--r-- 1 aikchar admin 0 Dec 9 09:58 __init__.py -rw-r--r-- 1 aikchar admin 2658 Dec 9 09:58 patch.py -rw-r--r-- 1 aikchar admin 20928 Dec 9 09:58 synchronize.py ``` In 2023, [new_stdin was deprecated](https://github.com/ansible/ansible/pull/79886/files) by Ansible. Following that, synchronize [removed new_stdin](https://github.com/ansible-collections/ansible.posix/pull/421) in 2024. So even though the newer versions of Ansible were packaging the version of synchronize that didn't have new_stdin, I was still running into the error. # Workaround Until I had figured out how to fix the issue, I still needed to be able to push contents of the website to the server. A manual workaround I used was to run `rsync` like so, ``` rsync -rv --delete --no-perms -e 'ssh -F /Users/aikchar/infra-as-code/ssh_config' --rsync-path 'sudo /usr/bin/rsync' /Users/aikchar/website-example.com/output/ aikchar@example.com:/srv/www/example.com/ ```