From infra
Deeper Ansible role and playbook audit beyond ansible-lint — idempotency smells, handler misuse, secret handling, become-scope, tag discipline.
How this skill is triggered — by the user, by Claude, or both
Slash command
/infra:review-ansible-playbooks [role or playbook path][role or playbook path]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Audit Ansible roles and playbooks for anti-patterns that `ansible-lint`
Audit Ansible roles and playbooks for anti-patterns that ansible-lint
does not catch — idempotency smells, handler misuse, unsafe secret
handling, overly broad become: scope, and undisciplined tag use. This
skill is strictly read-only, repo-local — it never SSHes to live
hosts, never executes playbooks against real inventory, and never applies
changes.
The user runs /review-ansible-playbooks with an optional path to a
single role (roles/common) or playbook (playbooks/site.yml). Without
an argument, scan every role and playbook discoverable under the
repository root.
For each step, report PASS, FINDING, or SKIPPED (nothing in scope for that category).
Enumerate roles and playbooks under the argument path. Default globs when no argument is given:
roles/*/tasks/*.yml, roles/*/handlers/*.yml,
roles/*/defaults/*.yml, roles/*/vars/*.yml,
roles/*/meta/main.yml, roles/*/templates/**/*.j2playbooks/**/*.yml, site.yml, *.ansible.ymlcollections/ansible_collections/*/*/roles/
when a requirements.yml or galaxy.yml is presentIf no Ansible content is found, mark the run SKIPPED and stop.
Grep task files for:
ansible.builtin.command: or ansible.builtin.shell: (including short
form command: / shell:) without any of creates:,
removes:, changed_when:, or a comment justifying non-idempotency.ansible.builtin.lineinfile: applied to a file that is also managed by
a nearby template: task — duplicate ownership.file: tasks that could collapse into a
template: or copy: with a loop.raw: used outside bootstrap contexts (flag and require comment).check_mode: false used to hide a non-idempotent task — flag with the
file:line and ask whether the logic can be rewritten idempotently.roles/*/handlers/main.yml, grep the
role's task files for matching notify: entries. Unused handler =
FINDING.notify: entry in tasks, verify a handler of that exact name
exists in the same role or is imported. Missing handler = FINDING.block: with several inner steps) should be
promoted to a role or a tasks file included with import_tasks:.Grep for:
password, passwd, secret, token,
api_key, apikey, private_key, passphrase declared in plain
defaults/main.yml, vars/main.yml, group_vars/*.yml, or
host_vars/*.yml without a $ANSIBLE_VAULT; header.lookup('env', '...') used in contexts where the surrounding task
lacks no_log: true.hashi_vault, aws_ssm, or community.hashi_vault.vault_read_secret
lookups in tasks without no_log: true.vault.yml, secrets.yml, or matching *secret*.yml that
are not encrypted (first-line check for $ANSIBLE_VAULT;) and that
appear in git ls-files (tracked).Each hit is a HIGH FINDING.
become: yes / become: true set at the play level when many tasks
within do not require elevation — suggest moving become: onto the
specific tasks that need it.become_user: omitted on an escalated task when root is not required
(e.g. managing a service account's config).become_method: su without justification (prefer sudo).become: contradictions between play, block, and task.tags: on plays, roles, blocks,
and tasks.# Usage: ansible-playbook ... --tags foo).tags: in a playbook that otherwise uses tags heavily =
FINDING.--tags contract
and the playbook uses tags, FINDING.Invoke ansible-lint --nocolor against the scoped path if the binary is
installed. Include its output in a dedicated subsection. Do not
replicate its rule checks in this skill's own findings — attribute each
ansible-lint hit to ansible-lint. If the binary is missing, mark the
subsection SKIPPED.
## Ansible Playbook Review — <path or repo root>
### Scope
Roles scanned: N
Playbooks scanned: N
### Findings by Role / Playbook
#### roles/common
- [FINDING] tasks/bootstrap.yml:14 — shell: without creates / changed_when
- [FINDING] handlers/main.yml — handler "restart app" declared, never notified
- [HIGH] vars/main.yml:3 — plain-text "api_token" (should be vaulted)
#### roles/nginx
- [FINDING] tasks/main.yml — play-level become: yes but 6 of 11 tasks do not require root
- [FINDING] tasks/main.yml — tasks missing tags while other tasks in the role are tagged
#### playbooks/site.yml
- [FINDING] header comment does not document --tags contract; tags "deploy", "config" referenced by tasks
### ansible-lint Output
<captured output, or SKIPPED: ansible-lint not installed>
**Totals:** FINDING: N, HIGH: N
**Verdict:** <PASS | CONCERN | FAIL>
no_log:, unencrypted vault.yml / secrets.yml
tracked in git).ansible-playbook
without --check. Never run ansible ad-hoc commands. Allowed
commands: ansible-inventory --list, ansible-lint,
ansible-playbook --check --diff. Nothing else.ansible-playbook ... without
--check. Do not propose ssh, rsync, or scp.ansible-lint, missing inventory, or
empty scope each degrade to SKIPPED for the relevant step.$ARGUMENTS
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub brenthaertlein/universal-skills --plugin infra