# Role variables not properly scoped: subsequent roles inherit variables from previous roles instead of loading their own

**URL:** <https://forum.ansible.com/t/role-variables-not-properly-scoped-subsequent-roles-inherit-variables-from-previous-roles-instead-of-loading-their-own/39749>\
**Category:** Get Help\
**Tags:** ubuntu\
**Created:** [January 21, 2025, 6:06pm UTC](https://forum.ansible.com/t/role-variables-not-properly-scoped-subsequent-roles-inherit-variables-from-previous-roles-instead-of-loading-their-own/39749 "2025-01-21T18:06:16Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![imcnish](https://avatars.discourse-cdn.com/v4/letter/i/8c91f0/32.png) [@imcnish](https://forum.ansible.com/u/imcnish)\
**Post date:** [January 21, 2025, 6:06pm UTC](https://forum.ansible.com/t/role-variables-not-properly-scoped-subsequent-roles-inherit-variables-from-previous-roles-instead-of-loading-their-own/39749/1 "2025-01-21T18:06:17Z")

</div>

According to Ansible’s variable precedence rules, variables defined in a role’s  
`vars/main.yml` should be scoped to that role. However, when multiple roles  
define the same variable name, a later role appears to inherit the variable  
value from the earlier role (not the problem) instead of loading its own  
role-scoped variables (the problem).

E.g.,  
`role1` has a variable named `users` in `role1/vars/main.yml`.

`role2` has a variable named `users` in `role2/vars/main.yml`.

First, `role1` executes, automatically loads `role1/vars/main.yml` and finds the  
role-local `users`.

Then, `role2` executes, inherits `users` from `role1` (which is okay), then  
should automatically load `role2/vars/main.yml`, find the role-local  
`users`, and replace `users` from `role1` with `users` from `role2`.

What I described appears to conform to Ansible’s variable precedence as  
described at →   
[https://docs.ansible.com/ansible/latest/playbook\_guide/playbooks\_variables.html#understanding-variable-precedence](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#understanding-variable-precedence)

For both role executions, `15. role vars (as defined in Role directory structure)` should define the precedence, thus the newer `users` should be used  
in `role2`. Nothing of lower precedence (p\<15) is at play, and nothing of  
higher precedence (p\>15) is at play.

As a practical matter, I understand that I can work around this behavior by  
using variable name based scoping, however I’m trying to understand why this is  
happening. (PEBCAK, bug, documentation bug, etc.)

Ansible: core 2.18.1  
Python: 3.9.6  
OS: Darwin 23.6.0

Reproducible example:

```auto
# test.yml
---
- name: Test playbook
  hosts: all
  become: true
  roles:
    - users_os_bundled
    - users_admin

# roles/users_os_bundled/tasks/main.yml
---
- name: Load OS distribution specific variables
  ansible.builtin.include_vars:
    file: "vars/distribution/{{ ansible_distribution }}.yml"
  when: ansible_distribution is defined

- name: Remove user and primary group
  include_tasks: roles/user_manage/tasks/remove_user.yml
  loop: "{{ users }}"
  tags: ['users']

# roles/users_os_bundled/vars/distribution/Ubuntu.yml
---
users:
  - name: ubuntu
    remove: true
  - name: test1
  - name: test2

# roles/users_admin/tasks/main.yml
---
- name: Add user and primary group
  include_tasks: roles/user_manage/tasks/install_user.yml
  loop: "{{ users }}"
  tags: ['users']

# roles/users_admin/vars/main.yml
---
users:
  - name: alice
    passwd: '<redacted>'
    uid: 1234
    comment: Alice
    shell: "{{ valid_shells['zsh'] }}"
    ssh_keys:
      - 'ssh-ed25519 <redacted>'

# roles/user_manage/tasks/remove_user.yml
---
- name: Remove user and primary group
  block:
    - name: Remove user "{{ item.name }}"
      ansible.builtin.user:
        name: "{{ item.name }}"
        state: absent
        groups: []
        remove: "{{ item.remove | default(false) }}"
        force: "{{ item.force | default(false) }}"

    - name: Call -> remove_group "{{ item.name }}"
      include_tasks: roles/user_manage/tasks/remove_group.yml

  rescue:
    - name: Report remove group failure
      debug:
        msg: >-
          Failed to remove user and primary group {{ item.name }}.
          This could leave orphaned resources.
          Error: {{ ansible_failed_result | default('unknown') }}

# roles/user_manage/tasks/remove_group.yml
---
- name: Remove group
  block:
    - name: Remove group '{{ item.name }}'
      ansible.builtin.group:
        name: "{{ item.name }}"
        state: absent

  when:
    - item.name is defined

  rescue:
    - name: Report remove group failure
      debug:
        msg: >-
          Failed to remove group {{ item.name }}.
          Error: {{ ansible_failed_result | default('unknown') }}

# roles/user_manage/tasks/install_user.yml
---
- name: Install user
  block:
    - name: Call -> install_group "{{ item.name }}"
      include_tasks: roles/user_manage/tasks/install_group.yml
      vars:
        group_name: "{{ item.name }}"
        group_gid: "{{ item.uid }}"

    - name: Install user "{{ item.name }}"
      ansible.builtin.user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        group: "{{ item.name }}"
        groups: "{{ item.groups | default(omit) }}"
        comment: "{{ item.comment | default(omit) }}"
        home: "{{ item.home | default(home_dir_prefix + '/' + item.name) }}"
        shell: "{{ item.shell | default(valid_shells.bash) }}"
        password: "{{ item.passwd | default(omit) }}"
        system: "{{ item.system | default(false) }}"
        state: present

    - name: Call -> install_ssh_keys for "{{ item.name }}"
      include_tasks: roles/user_manage/tasks/install_ssh_keys.yml
      when:
        - item.ssh_keys is defined
        - item.ssh_keys | length > 0

  when:
    - item.name is defined
    - item.comment is defined
    - item.home is defined

  rescue:
    - name: Report failure to install user and primary group
      debug:
        msg: >-
          Failed to install user {{ item.name }}.
          Error: {{ ansible_failed_result | default('unknown') }}

# roles/user_manage/tasks/install_group.yml
---
- name: Install group
  block:
    - name: Install group "{{ item.name }}"
      ansible.builtin.group:
        name: "{{ item.name }}"
        gid: "{{ item.gid | default(omit) }}"
        system: "{{ item.system | default(false) }}"
        state: present

  when:
    - item.name is defined

  rescue:
    - name: Report install group failure
      debug:
        msg: >-
          Failed to install group {{ item.name }}.
          Error: {{ ansible_failed_result | default('unknown') }}

# roles/user_manage/tasks/install_ssh_keys.yml
---
- name: Install SSH keys
  block:
    - name: Add SSH authorized keys for "{{ item.name }}"
      ansible.builtin.authorized_key:
        user: "{{ item.name }}"
        key: "{{ item.ssh_keys }}"
        manage_dir: true
        exclusive: true
        state: present

  when:
    - item.name is defined
    - item.ssh_keys is defined
    - item.ssh_keys | length > 0

  rescue:
      - name: Report install SSH authorized keys failure
        debug:
          msg: >-
            Failed to install SSH authorized keys for {{ item.name }}.
            Error: {{ ansible_failed_result | default('unknown') }}

```

Log:

```auto
% ansible-playbook test.yml -i inventory.yml --limit 10.1.2.3 -K --check
...
TASK [users_os_bundled : Load OS distribution specific variables] ************************************************************************************
ok: [10.1.2.3]

TASK [users_os_bundled : Remove user and primary group] **********************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_user.yml for 10.1.2.3 => (item={'name': 'ubuntu', 'remove': True})
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_user.yml for 10.1.2.3 => (item={'name': 'test1'})
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_user.yml for 10.1.2.3 => (item={'name': 'test2'})

TASK [users_os_bundled : Remove user "ubuntu"] *******************************************************************************************************
ok: [10.1.2.3]

TASK [users_os_bundled : Call -> remove_group "ubuntu"] **********************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_group.yml for 10.1.2.3

TASK [users_os_bundled : Remove group 'ubuntu'] ******************************************************************************************************
ok: [10.1.2.3]

TASK [users_os_bundled : Remove user "test1"] **********************************************************************************************************
changed: [10.1.2.3]

TASK [users_os_bundled : Call -> remove_group "test1"] *************************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_group.yml for 10.1.2.3

TASK [users_os_bundled : Remove group 'test1'] *********************************************************************************************************
changed: [10.1.2.3]

TASK [users_os_bundled : Remove user "test2"] ******************************************************************************************************
changed: [10.1.2.3]

TASK [users_os_bundled : Call -> remove_group "test2"] *********************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_group.yml for 10.1.2.3

TASK [users_os_bundled : Remove group 'test2'] *****************************************************************************************************
changed: [10.1.2.3]

TASK [users_admin : Debug users variable source] *****************************************************************************************************
skipping: [10.1.2.3]

TASK [users_admin : Add user and primary group] ******************************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/install_user.yml for 10.1.2.3 => (item={'name': 'ubuntu', 'remove': True})
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/install_user.yml for 10.1.2.3 => (item={'name': 'test1'})
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/install_user.yml for 10.1.2.3 => (item={'name': 'test2'})

TASK [users_admin : Call -> install_group "ubuntu"] **************************************************************************************************
skipping: [10.1.2.3]

TASK [users_admin : Install user "ubuntu"] ***********************************************************************************************************
skipping: [10.1.2.3]

TASK [users_admin : Call -> install_ssh_keys for "ubuntu"] *******************************************************************************************
skipping: [10.1.2.3]

TASK [users_admin : Call -> install_group "test1"] *****************************************************************************************************
skipping: [10.1.2.3]

TASK [users_admin : Install user "test1"] **************************************************************************************************************
skipping: [10.1.2.3]

TASK [users_admin : Call -> install_ssh_keys for "test1"] **********************************************************************************************
skipping: [10.1.2.3]

TASK [users_admin : Call -> install_group "test2"] *************************************************************************************************
skipping: [10.1.2.3]

TASK [users_admin : Install user "test2"] **********************************************************************************************************
skipping: [10.1.2.3]

TASK [users_admin : Call -> install_ssh_keys for "test2"] ******************************************************************************************
skipping: [10.1.2.3]
...

```

---

<div class="post-metadata">

**Author:** ![sivel](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/sivel/32/1198_2.png) [@sivel](https://forum.ansible.com/u/sivel)\
**Post date:** [January 21, 2025, 6:26pm UTC](https://forum.ansible.com/t/role-variables-not-properly-scoped-subsequent-roles-inherit-variables-from-previous-roles-instead-of-loading-their-own/39749/2 "2025-01-21T18:26:05Z")

</div>

Role vars are scoped to the play level, not the role. As such, the last defined role, overwrites the vars of previous roles.

You may want to utilize `include_role` instead, which defaults to not exposing it’s role vars, effectively keeping them scoped to the role itself.

---

<div class="post-metadata">

**Author:** ![imcnish](https://avatars.discourse-cdn.com/v4/letter/i/8c91f0/32.png) [@imcnish](https://forum.ansible.com/u/imcnish)\
**Post date:** [January 21, 2025, 8:34pm UTC](https://forum.ansible.com/t/role-variables-not-properly-scoped-subsequent-roles-inherit-variables-from-previous-roles-instead-of-loading-their-own/39749/3 "2025-01-21T20:34:10Z")

</div>

That’s my understanding as well, `Role vars are scoped to the play level` and `last defined role, overwrites the vars of previous roles`  
…but that is not what’s happening.

If the behavior was matching your statement, then (from my simple example) the `role2` variable named `users` would be overwriting the `role1` variable named `users`, but this is not what’s happening.

---

<div class="post-metadata">

**Author:** ![sivel](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/sivel/32/1198_2.png) [@sivel](https://forum.ansible.com/u/sivel)\
**Post date:** [January 21, 2025, 8:51pm UTC](https://forum.ansible.com/t/role-variables-not-properly-scoped-subsequent-roles-inherit-variables-from-previous-roles-instead-of-loading-their-own/39749/4 "2025-01-21T20:51:53Z")

</div>

Ah, I now see something I missed before. You are mixing role vars, and `include_vars`. `include_vars` (18) always win over role vars (15):

[https://docs.ansible.com/ansible/latest/playbook\_guide/playbooks\_variables.html#variable-precedence-where-should-i-put-a-variable](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)

I initially assumed both were role vars (`roles/main.yml`)

---

<div class="post-metadata">

**Author:** ![imcnish](https://avatars.discourse-cdn.com/v4/letter/i/8c91f0/32.png) [@imcnish](https://forum.ansible.com/u/imcnish)\
**Post date:** [January 21, 2025, 9:23pm UTC](https://forum.ansible.com/t/role-variables-not-properly-scoped-subsequent-roles-inherit-variables-from-previous-roles-instead-of-loading-their-own/39749/5 "2025-01-21T21:23:57Z")

</div>

Thanks for the help! Somehow in my brain I was not fully processing that I am actually calling `include_vars` because I am solely doing so to emulate an improved/more sane group\_vars functionality. Intent vs. reality. =(

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/ansible/original/1X/1882d9a01da5d8996a862359b0913cc568a39b84.svg) [@system](https://forum.ansible.com/u/system)\
**Post date:** [February 20, 2025, 9:24pm UTC](https://forum.ansible.com/t/role-variables-not-properly-scoped-subsequent-roles-inherit-variables-from-previous-roles-instead-of-loading-their-own/39749/6 "2025-02-20T21:24:22Z")

</div>

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
