ansible/ansible · error · UnarchiveError

Cannot change group ownership of %s to %s, as user %s

Error message

Cannot change group ownership of %s to %s, as user %s

What it means

Raised by the unarchive module when the archive contains files whose group ownership would need to change to a group that the executing (non-root) user does not belong to. The code first checks run_uid != 0, then verifies whether the future group/gid differs from the run group and is absent from the user's supplementary groups. It is an UnarchiveError that aborts the task before any extraction drift occurs.

Source

Thrown at lib/ansible/modules/unarchive.py:740

            if owner and owner != fut_owner:
                change = True
                err += 'Path %s is owned by user %s, not by user %s as expected\n' % (path, owner, fut_owner)
                itemized[6] = 'o'
            elif uid and uid != fut_uid:
                change = True
                err += 'Path %s is owned by uid %s, not by uid %s as expected\n' % (path, uid, fut_uid)
                itemized[6] = 'o'

            # Compare file group ownership
            group = gid = None
            try:
                group = grp.getgrgid(st.st_gid).gr_name
            except (KeyError, ValueError, OverflowError):
                gid = st.st_gid

            if run_uid != 0 and (fut_group != run_group or fut_gid != run_gid) and fut_gid not in groups:
                raise UnarchiveError('Cannot change group ownership of %s to %s, as user %s' % (path, fut_group, run_owner))

            if group and group != fut_group:
                change = True
                err += 'Path %s is owned by group %s, not by group %s as expected\n' % (path, group, fut_group)
                itemized[6] = 'g'
            elif gid and gid != fut_gid:
                change = True
                err += 'Path %s is owned by gid %s, not by gid %s as expected\n' % (path, gid, fut_gid)
                itemized[6] = 'g'

            # Register changed files and finalize diff output
            if change:
                if path not in self.includes:
                    self.includes.append(path)
                diff += '%s %s\n' % (''.join(itemized), path)

        if self.includes:
            unarchived = False

View on GitHub (pinned to 9cf16a4aca)

Solutions

  1. Run the task with become: true (root can chgrp to any group), or delegate to a host where the user has the needed rights
  2. Add the executing user to the target group (usermod -aG <group> <user>) and re-login so the supplementary group list includes fut_gid
  3. Rebuild/normalize the archive so its group ownership matches the extracting user's group (tar --group= on creation, or chgrp -R the tree before archiving)
  4. Set group ownership/sticky behaviors via the module's owner/group arguments consistent with the run user's memberships

Example fix

# before
- name: extract app tarball
  ansible.builtin.unarchive:
    src: app.tar.gz
    dest: /opt/app
  # runs as non-root 'deploy', archive gid is 'apache'

# after
- name: extract app tarball
  ansible.builtin.unarchive:
    src: app.tar.gz
    dest: /opt/app
  become: true
  # or: add deploy to apache group and re-run
Defensive patterns

Strategy: validation

Validate before calling

# Before unarchive as non-root, confirm membership in the archive's group
import grp, os

def can_chgrp(path_gid, run_gid=None):
    run_gid = run_gid if run_gid is not None else os.getgid()
    groups = {g.gr_gid for g in grp.getgrall() if os.getlogin() in g.gr_mem}
    groups.add(run_gid)
    return path_gid in groups or os.getuid() == 0

# in the task: fail fast with a clear message
- ansible.builtin.stat: {path: /tmp/app.tar.gz}
  register: st

Prevention

When it happens

Trigger: Running unarchive as a non-root user (no become) where the archive records a group different from the run group and the fut_gid is not in the groups obtained for the user; e.g. archive built with group 'apache' extracted by user 'deploy' that is only in 'deploy'.

Common situations: CI pipelines extracting tarballs as an unprivileged service user; archives created on a different host with different group names/gids; hardening setups that forbid become; check_mode runs still evaluate this ownership guard.

Related errors


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