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'] = False

View on GitHub (pinned to 9cf16a4aca)

Solutions

  1. Confirm the user can read the file: run cat on it as that user from the host
  2. Add become: true for privileged files
  3. Check for symlink oddities (ls -l path) and target the real file
  4. 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

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


AI-assisted analysis of ansible/ansible@9cf16a4aca (2026-08-15). Data as JSON: /api/errors/ba90749119e690f0. Report an issue: GitHub.