Default Rules

Below you can see the list of default rules Ansible Lint use to evaluate playbooks and roles:


Using command rather than module.

Executing a command when there is an Ansible module is generally a bad idea


Use shell only when shell functionality is required.

Shell should only be used when piping, redirecting or chaining commands (and Ansible would be preferred for some of those!)


This rule identifies possible confusing expressions where it is not clear if a variable or string is to be used and asks for clarification.

You should either use the full variable syntax (‘{{{{ {0} }}}}’) or, whenever possible, convert it to a list of strings.

Problematic code

- ansible.builtin.debug:
    msg: "{{ item }}"
  with_items: foo # <-- deprecated-bare-vars

Correct code

# if foo is not really a variable:
- ansible.builtin.debug:
    msg: "{{ item }}"
    - foo

# if foo is a variable:
- ansible.builtin.debug:
    msg: "{{ item }}"
  with_items: "{{ foo }}"


Using command rather than an argument to e.g. file.

Executing a command when there are arguments to modules is generally a bad idea


Do not use ‘local_action’, use ‘delegate_to: localhost’.

Do not use local_action, use delegate_to: localhost


Deprecated module.

These are deprecated modules, some modules are kept temporarily for backwards compatibility but usage is discouraged. more


Use FQCN for builtin actions.

Check whether the long version starting with ansible.builtin is used in the playbook


Git checkouts must contain explicit version.

All version control checkouts must point to an explicit commit or tag, not just latest


Mercurial checkouts must contain explicit revision.

All version control checkouts must point to an explicit commit or tag, not just latest


Use failed_when and specify error conditions instead of using ignore_errors.

Instead of ignoring all errors, ignore the errors only when using {{ ansible_check_mode }}, register the errors using register, or use failed_when: and specify acceptable error conditions to reduce the risk of ignoring important failures.


Command module does not accept setting environment variables inline.

Use environment: to set environment variables or use shell module which accepts both


Unexpected internal error.

This error can be caused by internal bugs but also by custom rules. Instead of just stopping linter we generate the errors and continue processing. This allows users to add this rule to their warn list until the root cause is fixed.


Ensure specific order of keys in mappings.


Don’t compare to literal True/False.

Use when: var rather than when: var == True (or conversely when: not var)


Linter failed to process a YAML file, probably because it is either:

  • contains unsupported encoding (only UTF-8 is supported)

  • not an Ansible file

  • it contains some unsupported custom YAML objects (!! prefix)

  • it was not able to decrypt an inline !vault block.

This violation is not skippable, so it cannot be added to the warn_list or the skip_list. If a vault decryption issue cannot be avoided, the offending file can be added to exclude_paths configuration.


meta/main.yml default values should be changed.

meta/main.yml default values should be changed for: author, company, description, license


meta/main.yml should contain relevant info.

meta/main.yml should contain: author, description, license, min_ansible_version, platforms


Tags must contain lowercase letters and digits only.

Tags must contain lowercase letters and digits only, and galaxy_tags is expected to be a list


Commands should not change things if nothing needs doing.

Tasks should tell Ansible when to return changed, unless the task only reads information. To do this, set changed_when, use the creates or removes argument, or use when to run the task only if another check has a particular result.

For example, this task registers the shell output and uses the return code to define when the task has changed.

    - name: handle shell output with return code cat {{ my_file|quote }}
      register: my_output
      changed_when: my_output.rc != 0

The following example will trigger the rule since the task does not handle the output of the command.

    - name: does not handle any output or return codes
      ansible.builtin.command: cat {{ my_file|quote }}


Tasks that run when changed should likely be handlers.

If a task has a when: result.changed setting, it is effectively acting as a handler. You could use notify and move that task to handlers. more


Nested jinja pattern.

There should not be any nested jinja pattern. Example (bad): {{ list_one + {{ list_two | max }} }}, Example (good): {{ list_one + max(list_two) }}, Example (allowed): --format='{{'{{'}}.Size{{'}}'}}'


No Jinja2 in when.

when is a raw Jinja2 expression, remove redundant {{ }} from variable(s).


Role loop_var should use configured prefix.

Looping inside roles has the risk of clashing with loops from user-playbooks. more


Doesn’t need a relative path in role.

copy and template do not need to use relative path for src


Most files should not contain tabs.

Tabs can cause unexpected display issues, use spaces


Package installs should not use latest.

Package installs should use state=present with or without a version



Ansible parser fails; this usually indicates an invalid file.


become_user requires become to work as expected.

become_user without become will not actually change user


Use “.yml” or “.yaml” playbook extension.

Playbooks should have the “.yml” or “.yaml” extension


File permissions unset or incorrect.

Missing or unsupported mode parameter can cause unexpected file permissions based on version of Ansible being used. Be explicit, like mode: 0644 to avoid hitting this rule. Special preserve value is accepted only by copy, template modules. more


Octal file permissions must contain leading zero or be a string.

Numeric file permissions without leading zero can behave in unexpected ways. more


Shells that use pipes should set the pipefail option.

Without the pipefail option set, a shell command that implements a pipeline can fail and still return 0. If any part of the pipeline other than the terminal command fails, the whole pipeline will still return 0, which may be considered a success by Ansible. Pipefail is available in the bash shell.


Role name {0} does not match ^[a-z][a-z0-9_]+$ pattern.

Role names are now limited to contain only lowercase alphanumeric characters, plus underline and start with an alpha character. more


Perform JSON Schema Validation for known lintable kinds.

Returned errors will not include exact line numbers, but they will mention the schema name being used as a tag, like playbook-schema, tasks-schema.

This rule is not skippable and stops further processing of the file.

Schema bugs should be reported towards schemas project instead of ansible-lint.

If incorrect schema was picked, you might want to either:

  • move the file to standard location, so its file is detected correctly.

  • use kinds: option in linter config to help it pick correct file type.


Ansible syntax check failed.

Running ansible-playbook --syntax-check ... failed.

This error cannot be disabled due to being a prerequisite for other steps. You can either exclude these files from linting or better assure they can be loaded by Ansible. This is often achieved by editing inventory file and/or ansible.cfg so ansible can load required variables.

If undefined variables are the failure reason you could use jinja default() filter in order to provide fallback values.


All tasks should be named.

All tasks should have a distinct name for readability and for --start-at-task to work


All variables should be named using only lowercase and underscores.


Variables and filters in Jinja2 should have spaces before and after, like {{ var_name | filter }}.. This improves readability and makes it less likely to introduce typos.

Problematic code

foo: "{{some|dict2items}}"

Correct code

foo: "{{ some | dict2items }}"


Our linter also includes violations reported by yamllint but it uses a slightly different default configuration. We will still load custom yamllint configuration files, but the defaults come from ansible-lint, not from yamllint.

You can fully disable all yamllint violations by adding yaml to the skip_list.

Specific tag identifiers that are printed at the end of rule name, like yaml[trailing-spaces] or yaml[indentation] can also be be skipped, allowing you to have a more fine control.

Problematic code

# missing document-start
foo: ...
foo: ...  # <-- key-duplicates
bar: ...       # <-- wrong comment indentation

Correct code

foo: ...
bar: ... # comment