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: HEAD is specified
  • No revision is 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:

  1. Changeset hash (most reproducible) — a1b2c3d4e5f6
  2. Tag (good for releases) — v2.1.0
  3. 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:

RuleIDTarget
latest[git]401Git repositories (ansible.builtin.git)
latest[hg]402Mercurial 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"

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.