Introduction
Ansible Lint rule 402, latest[hg], flags playbook tasks that use unpinned Mercurial (hg) repository checkouts. Using HEAD or leaving the revision unspecified means your playbook's behavior depends on whatever happens to be the latest commit — making it non-reproducible and potentially breaking.
This rule is part of the broader latest family that also covers Git repositories (latest[git]).
The Rule
Rule ID: 402
Name: latest[hg]
Description: Ensures that community.general.hg module calls specify a pinned revision
Severity: Warning
Tags: idempotency
The rule triggers when:
revision: HEADis specified- No
revisionis specified (defaults to tip/HEAD)
Problematic Code
---
- name: Example playbook
hosts: all
tasks:
- name: Risky — unpinned revision
community.general.hg:
repo: "https://hg.example.com/myproject"
dest: /opt/myproject
revision: HEAD # ← Rule 402 triggers here
Why this is problematic:
HEAD(or tip) changes with every commit- Running the same playbook today and tomorrow may produce different results
- No way to trace which version is deployed
- Rollbacks become impossible
Correct Code
---
- name: Example playbook
hosts: all
tasks:
- name: Safe — pinned to specific changeset
community.general.hg:
repo: "https://hg.example.com/myproject"
dest: /opt/myproject
revision: "a1b2c3d4e5f6" # ← Specific changeset hash
Other Valid Pinning Methods
# Pin to a tag (release version)
- name: Pin to release tag
community.general.hg:
repo: "https://hg.example.com/myproject"
dest: /opt/myproject
revision: "v2.1.0"
# Pin to a named branch (less ideal — still moves)
- name: Pin to branch
community.general.hg:
repo: "https://hg.example.com/myproject"
dest: /opt/myproject
revision: "stable"
# Pin to specific changeset number
- name: Pin to changeset number
community.general.hg:
repo: "https://hg.example.com/myproject"
dest: /opt/myproject
revision: "1234"
Best practice ranking:
- Changeset hash (most reproducible) —
a1b2c3d4e5f6 - Tag (good for releases) —
v2.1.0 - Branch name (least reproducible — still moves) —
stable
When to Skip the Rule
If you intentionally want the latest code (e.g., development environments), suppress the warning:
# Per-task skip
- name: Get latest code (dev only)
community.general.hg:
repo: "https://hg.example.com/myproject"
dest: /opt/myproject
tags: [dev] # noqa: latest[hg]
Or in .ansible-lint configuration:
# .ansible-lint
skip_list:
- latest[hg]
Or skip all latest rules:
skip_list:
- latest
The latest Rule Family
Rule 402 is part of a family of rules that enforce pinned source control references:
| Rule | ID | Target |
|---|---|---|
latest[git] | 401 | Git repositories (ansible.builtin.git) |
latest[hg] | 402 | Mercurial repositories (community.general.hg) |
Both follow the same principle: pin your source control references for reproducible automation.
Git Equivalent
# WRONG — triggers latest[git]
- name: Risky git checkout
ansible.builtin.git:
repo: https://github.com/org/repo.git
dest: /opt/repo
version: HEAD
# CORRECT
- name: Pinned git checkout
ansible.builtin.git:
repo: https://github.com/org/repo.git
dest: /opt/repo
version: "v2.1.0" # or specific commit SHA
Using Variables for Flexibility
Pin the revision but make it configurable:
# group_vars/all.yml
myproject_version: "a1b2c3d4e5f6"
# playbook.yml
- name: Deploy pinned version
community.general.hg:
repo: "https://hg.example.com/myproject"
dest: /opt/myproject
revision: "{{ myproject_version }}"
Update the version in one place, and all environments use the same pinned revision.
Finding the Current Revision
# Get current changeset hash
hg id -i
# Get current tag
hg id -t
# Show full log of recent commits
hg log -l 5 --template "{node|short} {desc|firstline}\n"
Related Articles
- Ansible Lint Rule 302: deprecated-command-syntax
- Ansible Lint Rule use-loop
- Ansible Error Handling Guide
- Ansible Best Practices Guide
- Ansible Galaxy Guide
- VS Code for Ansible Development
- Ansible Debug Module Guide
Conclusion
Ansible Lint rule 402 (latest[hg]) enforces a fundamental DevOps principle: pin your dependencies. Using HEAD or unspecified revisions in Mercurial checkouts creates non-reproducible playbooks that can break silently. The fix is simple — specify a changeset hash, tag, or at minimum a branch name. Use variables to keep pinned versions configurable across environments, and only skip the rule for intentional "always latest" development scenarios.