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 = FalseView on GitHub (pinned to 9cf16a4aca)
Solutions
- Run the task with become: true (root can chgrp to any group), or delegate to a host where the user has the needed rights
- Add the executing user to the target group (usermod -aG <group> <user>) and re-login so the supplementary group list includes fut_gid
- 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)
- 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
- Run unarchive with become: true when archives carry foreign group ownership
- Standardize archive creation with tar --group=<run_group> so ownership matches the extracting user
- Document required group memberships in role meta and assert them with a pre-task checking `id -nG`
- Avoid check_mode surprises: this ownership guard is evaluated even when nothing is extracted
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
- Cannot change ownership of %s to %s, as user %s
- LookupPrivilegeName() failed
- Failed to remove role %s: %s
- Unable to write to destination file {outfile!r}.
- Could not update files in %s: %s
AI-assisted analysis of ansible/ansible@9cf16a4aca (2026-08-15).
Data as JSON: /api/errors/1292a460df075508.
Report an issue: GitHub.