Skip to content

modules

**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible

Agent definition

modules.md

Ansible Modules Reference

> **Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes > **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) > **Generated**: 2026-04-04 — verify against current Ansible module documentation

---

Overview

The most common Ansible failure mode is using `command` or `shell` when an idempotent module exists. Module selection determines whether playbooks are idempotent, properly report changes, and work across environments. The hierarchy is: builtin module > collection module > command/shell with explicit `changed_when`.

---

Pattern Table: Module Selection by Task Type

| Task | Wrong Approach | Correct Module | Notes | |------|---------------|----------------|-------| | Install package (Debian) | `command: apt-get install nginx` | `ansible.builtin.apt` | Handles idempotency, state management | | Install package (RHEL) | `command: yum install nginx` | `ansible.builtin.dnf` | `dnf` preferred over `yum` on RHEL 8+ | | Install package (any OS) | `shell: {{pkg_mgr}} install` | `ansible.builtin.package` | Cross-platform, uses detected pkg manager | | Manage service | `command: systemctl start nginx` | `ansible.builtin.systemd` | Reports enabled/started/stopped state | | Copy file | `command: cp src dst` | `ansible.builtin.copy` | Detects changes by checksum | | Template | `command: sed 's/VAR/val/g' > /etc/conf` | `ansible.builtin.template` | Jinja2, detects changes, `--diff` support | | Create dir | `command: mkdir -p /path` | `ansible.builtin.file` | state=directory, handles permissions | | Download file | `command: wget url -O dest` | `ansible.builtin.get_url` | Checksum validation, change detection | | Run script once | `shell: /path/to/setup.sh` | `command` + `creates:` | `creates:` makes it idempotent | | Git checkout | `command: git clone` | `ansible.builtin.git` | Version-aware, detects changes | | Manage user | `command: useradd -m user` | `ansible.builtin.user` | Full lifecycle, idempotent | | Manage cron | `command: crontab -e` | `ansible.builtin.cron` | Named entries, idempotent |

---

Correct Patterns

Package Installation Across OS Families

# Use package module for cross-platform playbooks
- name: Install common packages
  ansible.builtin.package:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - python3
    - curl

# Use platform-specific modules when you need platform features
- name: Install nginx with apt options (Debian/Ubuntu)
  ansible.builtin.apt:
    name: nginx
    state: present
    install_recommends: false  # apt-specific option
  when: ansible_os_family == "Debian"

- name: Install nginx with dnf options (RHEL 8+)
  ansible.builtin.dnf:
    name: nginx
    state: present
    enablerepo: epel  # dnf-specific option
  when: ansible_distribution_major_version | int >= 8 and ansible_os_family == "RedHat"

**Why**: `package` abstracts over OS differences. Use `apt`/`dnf` only when platform-specific options (repos, recommends, module streams) are needed.

---

Service Management with systemd

# Start and enable in one task
- name: Enable and start nginx
  ansible.builtin.systemd:
    name: nginx
    state: started
    enabled: true
    daemon_reload: true  # Required when unit files change

# Restart via handler (triggered by config change)
- name: Deploy nginx config
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    mode: '0644'
  notify: Restart nginx

# handlers/main.yml
- name: Restart nginx
  ansible.builtin.systemd:
    name: nginx
    state: restarted

**Why**: Handlers ensure services restart only when the triggering resource actually changed. Using `state: restarted` directly in tasks causes restarts on every run.

---

Making `command` Idempotent with `creates`

# When no module exists, use command with creates/removes
- name: Run database migration
  ansible.builtin.command:
    cmd: /app/bin/migrate up
    chdir: /app
    creates: /app/.migrations-complete  # Skip if this file exists

# After migration, create the sentinel file
- name: Mark migrations complete
  ansible.builtin.file:
    path: /app/.migrations-complete
    state: touch
    mode: '0644'

# For scripts that can be re-run safely
- name: Check if cluster is initialized
  ansible.builtin.command: kubectl get nodes
  register: cluster_check
  changed_when: false
  failed_when: false

- name: Initialize cluster
  ansible.builtin.command: kubeadm init --config /etc/kubeadm/config.yml
  when: cluster_check.rc != 0

---

Pattern Catalog

Use Package Modules for Installation

**Detection**:

# Find shell/command used for package installation
grep -rn "command:\|shell:" playbooks/ roles/ \
  | grep -E "apt-get|yum|dnf|pip install|npm install"

rg -t yaml '(command|shell):.*\b(apt-get|yum|dnf|pip)\b' roles/ playbooks/

**Signal**:

- name: Install nginx
  shell: apt-get install -y nginx

- name: Update packages
  command: yum update -y

**Why this matters**: `shell`/`command` always report `changed` regardless of whether nginx was already installed. Running twice installs twice (or errors). No change tracking. Breaks `--check` mode (would show false changes).

**Preferred action**:

- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present
    update_cache: true
    cache_valid_time: 3600  # Only update cache if older than 1 hour

---

Use the systemd Module for Service Management

**Detection**:

grep -rn "command:\|shell:" roles/ playbooks/ \
  | grep "systemctl"

rg -t yaml '(command|shell):.*systemctl' roles/ playbooks/

**Signal**:

- name: Start nginx
  command: systemctl start nginx

- name: Enable nginx on boot
  shell: systemctl enable nginx

**Why this matters**: Doesn't report the service state as a change properly. Can't use `--check` mode (would actually run `s

Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked