ansible/ansible · error · Exception
Unable to read the contents of {path!r}.
Error message
Unable to read the contents of {path!r}. What it means
The replace module raises this Exception after its own directory/exists pre-checks pass but open(path).read() still fails with OSError. Since existence was just verified, the realistic causes are permission denied, a race where the file vanished, or a read error (I/O error, SELinux denial).
Source
Thrown at lib/ansible/modules/replace.py:260
res_args = dict(rc=0)
contents = None
params['after'] = to_text(params['after'], errors='surrogate_or_strict', nonstring='passthru')
params['before'] = to_text(params['before'], errors='surrogate_or_strict', nonstring='passthru')
params['regexp'] = to_text(params['regexp'], errors='surrogate_or_strict', nonstring='passthru')
params['replace'] = to_text(params['replace'], errors='surrogate_or_strict', nonstring='passthru')
if os.path.isdir(path):
module.fail_json(rc=256, msg='Path %s is a directory !' % path)
if not os.path.exists(path):
module.fail_json(rc=257, msg='Path %s does not exist !' % path)
else:
try:
with open(path, 'r', encoding=encoding) as f:
contents = f.read()
except OSError as ex:
raise Exception(f"Unable to read the contents of {path!r}.") from ex
pattern = u''
if params['after'] and params['before']:
pattern = u'%s(?P<subsection>.*?)%s' % (params['after'], params['before'])
elif params['after']:
pattern = u'%s(?P<subsection>.*)' % params['after']
elif params['before']:
pattern = u'(?P<subsection>.*)%s' % params['before']
if pattern:
section_re = re.compile(pattern, re.DOTALL)
match = re.search(section_re, contents)
if match:
section = match.group('subsection')
indices = [match.start('subsection'), match.end('subsection')]
else:
res_args['msg'] = 'Pattern for before/after params did not match the given file: %s' % pattern
res_args['changed'] = FalseView on GitHub (pinned to 9cf16a4aca)
Solutions
- Confirm the user can read the file: run cat on it as that user from the host
- Add become: true for privileged files
- Check for symlink oddities (ls -l path) and target the real file
- Look for SELinux denials (ausearch -m avc) if DAC looks fine
Example fix
# before
- ansible.builtin.replace:
path: /etc/nginx/nginx.conf
regexp: 'listen 80'
replace: 'listen 8080'
# after
- name: same edit with privilege
become: true
ansible.builtin.replace:
path: /etc/nginx/nginx.conf
regexp: 'listen 80'
replace: 'listen 8080' Defensive patterns
Strategy: validation
Validate before calling
import os
def replace_target_readable(path):
return os.path.isfile(path) and os.access(path, os.R_OK) Try / catch
try:
with open(path, 'r', encoding=encoding) as f:
contents = f.read()
except OSError as ex:
raise Exception(f'Unable to read the contents of {path!r}.') from ex Prevention
- Use become for privileged config files
- Verify readability as the connection user before editing
- Avoid racing concurrent deletions of the target file
When it happens
Trigger: replace on a file the module user cannot read (root-owned 0600 without become), a symlink loop, or the file being deleted between the exists check and the read.
Common situations: Editing /etc files while connecting as a non-root user without become: true; SELinux confining the ansible SSH session; path is a dangling symlink that exists but open fails.
Related errors
- LookupPrivilegeName() failed
- Failed to remove role %s: %s
- Unable to write to destination file {outfile!r}.
- Could not update files in %s: %s
- Could not make backup of {fn!r} to {backupdest!r}.
AI-assisted analysis of ansible/ansible@9cf16a4aca (2026-08-15).
Data as JSON: /api/errors/ba90749119e690f0.
Report an issue: GitHub.