Ansible core on the ansible/ansible devel branch took five commits this week, 22 files, 131 insertions, 70 deletions. The change operators will feel is a become bug in the SSH connection plugin. Extra args that happen to contain the ssh executable name were treated as if the process itself was ssh, which sent sftp and scp transfers down the privilege escalation prompt path.
SSH become trips on extra args that mention ssh ¶
The SSH plugin decided whether to wait for a become prompt with a membership test on the full command list. Checking ssh_executable in cmd is true whenever that name appears anywhere in argv, not only as argv[0]. People pass -S ssh to sftp so sftp uses ssh as the transport. Copy with become then launched sftp, saw ssh later in the list, and entered awaiting_prompt or awaiting_escalation. That is issue 87272. The changelog fragment names the same trigger for scp_extra_args.
The become detection fix in ssh.py compares only the first argument:
is_ssh = to_bytes(self.get_option('ssh_executable')) == cmd[0]
if is_ssh and sudoable:
The old string or bytes coercion at the top of _bare_run is gone. Unit tests now pass [b"ssh"] into _run instead of a bare string, so that path already expects a list of bytes.
A new integration playbook sets ansible_sftp_extra_args to -S ssh, copies a file with become: true on localhost, and is called from runme.sh. If copy or fetch with become hangs while extra args include the ssh executable name, this is the bug. It is on devel. It is not in a release.
File module state docs now match the return payload ¶
The file module docs update on file.py does not change module code. It rewrites the state option as a map of choices and fills in return docs that were missing.
Each choice now states the contract. absent unlinks files and deletes directories recursively and does not fail if the path is already gone. directory creates intermediate directories with the given permissions. file is a query when no other options are set. hard and link point at src. touch creates an empty file or updates times and always reports changed. All values except absent are documented as enforcing mode, owner, and access_time when those options apply. When state is omitted, the current state on the target is the desired state.
The old prose said state=file will not create a missing path and told people to use touch, copy, or template. That warning is no longer in the state field. Behavior did not change. The docs just stopped repeating it there.
Two return fields are new in the DOCUMENTATION string. src is the link target for state=link and state=hard. state is documented as always present for absent, otherwise only when the path exists, and it can differ from the requested value. A dangling symlink is the example. Playbooks that register file and branch on result.state should treat a missing state key as a real case, not a docs gap.
ansible-test pins sanity tools and skips mypy 1.20 ¶
The sanity requirements update is a pin refresh, not a new test. ansible-doc requirements move packaging from 26.0 to 26.3 across ansible-doc, changelog, black, package data, and the bundled updater. pylint requirements go 4.0.5 to 4.0.8 and isort jumps 8.0.1 to 9.0.1. black requirements go 26.3.1 to 26.5.1. The mypy input pin is mypy == 1.19.1 with an explicit comment: skip 1.20.x, more work needed for mypy 2.x. The generated mypy env also moves cryptography 46.0.5 to 50.0.1 and urllib3 2.6.3 to 2.7.0. Those pins are for the type checker env, not for Ansible runtime.
Collection authors who run ansible-test sanity will pick this up on the next devel pull. The isort major bump is the one most likely to rewrite import blocks. Do not assume mypy 1.20 will pass.
The urls unit test change in test_Request.py is test only as well. Older CPython keys HTTP auth as (('ansible.com', '/'),). Newer builds key it as ((None, 'ansible.com', '/'),). The tests now accept either. module_utils.urls.Request itself did not change.
Alpine cron tests die after libfaketime closes a lock fd ¶
Alpine 3.24 started failing the cron integration test for a reason that looks like a timeout and is not. The setup_cron target used nohup crond on Alpine. The test waits for a canary file with time sped up through LD_PRELOAD and libfaketime. That wait started hitting a 20 second timeout because crond never scheduled the job.
The cause is a crash, not a slow timer. libfaketime 0.9.13 switched its stat lock from a POSIX semaphore to flock() on a file in /dev/shm, held on an open fd cached in the library. That package landed in the Alpine community repo on 31 August 2026. busybox crond, started with nohup crond, daemonizes itself and closes every inherited fd above stderr. That loop closes the lock fd. The next faked fstat() calls flock() on a closed fd, gets EBADF, and libfaketime aborts crond with exit(1).
The Alpine cron test fix runs crond -f backgrounded by the shell, which skips daemonize so the lock fd stays open. The commit message records three checks: pin libfaketime below 0.9.13, set NO_FAKE_STAT=1, and the upstream flock change. This does not change the cron module. It changes how Ansible’s own Alpine tests start crond. If your image tests start busybox crond with nohup under libfaketime 0.9.13, expect the same silent exit.
What to watch ¶
None of this is in a stable tag. Treat it as devel until it lands in a release.
Playbooks that mix become with sftp_extra_args or scp_extra_args that mention the ssh executable should be retested against devel if copy or fetch has been hanging. Collection CI that runs ansible-test sanity will pick up isort 9, pylint 4.0.8, black 26.5.1, and a mypy pin at 1.19.1. Expect import sort diffs. Do not plan on mypy 1.20 yet.
Alpine cron failures that look like timeouts after 31 August 2026 are worth checking for libfaketime 0.9.13 before adding sleep.