Introduction

Ansible Lint rule 302, deprecated-command-syntax, flags the use of shorthand (free-form) syntax for command-line modules within playbooks. While shorthand is convenient on the command line, it creates hard-to-debug playbooks by mixing commands with module parameters in a single string. This guide shows how to convert shorthand to structured syntax.

The Rule

Rule ID: 302 Name: deprecated-command-syntax Description: Using command rather than an ideally suited module is acceptable, but shorthand syntax should not be used inside playbooks Severity: Error Tags: command-shell, deprecations

Problematic Code

---
- name: Example playbook
  hosts: all
  tasks:
    - name: Perform chmod
      ansible.builtin.command: creates=B chmod 644 A  # ← shorthand

In this example, creates=B and chmod 644 A are mixed in a single free-form string. Ansible must parse this ambiguously — is creates=B a parameter or part of the command?

More problematic examples:

# Shorthand with multiple parameters
- name: Run script
  ansible.builtin.command: chdir=/opt creates=/opt/.done ./install.sh

# Shell shorthand
- name: Check process
  ansible.builtin.shell: warn=false ps aux | grep nginx

# Raw shorthand
- name: Remote command
  ansible.builtin.raw: executable=/bin/bash echo hello

Correct Code

Convert shorthand parameters to the args block:

---
- name: Example playbook
  hosts: all
  tasks:
    - name: Perform chmod
      ansible.builtin.command: chmod 644 A
      args:
        creates: B

Or use the fully structured format:

- name: Perform chmod
  ansible.builtin.command:
    cmd: chmod 644 A
    creates: B

More Corrections

# BEFORE (shorthand)
- name: Run install script
  ansible.builtin.command: chdir=/opt creates=/opt/.done ./install.sh

# AFTER (structured)
- name: Run install script
  ansible.builtin.command:
    cmd: ./install.sh
    chdir: /opt
    creates: /opt/.done
# BEFORE (shorthand)
- name: Check process
  ansible.builtin.shell: warn=false ps aux | grep nginx

# AFTER (structured)
- name: Check process
  ansible.builtin.shell:
    cmd: ps aux | grep nginx
# BEFORE (shorthand)
- name: Run remote command
  ansible.builtin.raw: executable=/bin/bash echo hello

# AFTER (structured)
- name: Run remote command
  ansible.builtin.raw: echo hello
  args:
    executable: /bin/bash

Affected Modules

This rule applies to all command-line modules that support free-form syntax:

ModuleCommon Shorthand Parameters
ansible.builtin.commandcreates, removes, chdir, stdin
ansible.builtin.shellcreates, removes, chdir, executable
ansible.builtin.rawexecutable
ansible.builtin.scriptcreates, removes, chdir, executable

Why Shorthand Is Problematic

Ambiguity

# Is "creates=/tmp/output" a parameter or part of the command?
ansible.builtin.command: creates=/tmp/output echo "creates=/tmp/output"

Ansible has to guess whether creates= is a module parameter or literal text being passed to the shell.

Hard to Read

# Shorthand — what does this do?
ansible.builtin.command: chdir=/opt creates=/opt/.installed removes=/tmp/installer.sh ./setup.sh --prefix=/usr/local

# Structured — immediately clear
ansible.builtin.command:
  cmd: ./setup.sh --prefix=/usr/local
  chdir: /opt
  creates: /opt/.installed
  removes: /tmp/installer.sh

Hard to Maintain

With structured syntax, adding or removing parameters is clean:

# Easy to add/remove parameters
ansible.builtin.command:
  cmd: ./deploy.sh
  chdir: /opt/app
  creates: /opt/app/.deployed    # Easy to comment out
  # removes: /tmp/deploy-flag   # Easy to add back

Complete Example: Before and After

Before (Multiple Shorthand Issues)

---
- name: Deploy application
  hosts: all
  tasks:
    - name: Download
      ansible.builtin.command: chdir=/tmp creates=/tmp/app.tar.gz curl -O https://example.com/app.tar.gz

    - name: Extract
      ansible.builtin.command: chdir=/tmp creates=/opt/app tar xzf app.tar.gz -C /opt/

    - name: Install
      ansible.builtin.shell: chdir=/opt/app warn=false ./install.sh 2>&1 | tee /var/log/install.log

    - name: Cleanup
      ansible.builtin.command: removes=/tmp/app.tar.gz rm /tmp/app.tar.gz

After (Structured Syntax)

---
- name: Deploy application
  hosts: all
  tasks:
    - name: Download
      ansible.builtin.get_url:  # Better: use dedicated module
        url: https://example.com/app.tar.gz
        dest: /tmp/app.tar.gz

    - name: Extract
      ansible.builtin.unarchive:  # Better: use dedicated module
        src: /tmp/app.tar.gz
        dest: /opt/
        remote_src: true
        creates: /opt/app

    - name: Install
      ansible.builtin.shell:
        cmd: ./install.sh 2>&1 | tee /var/log/install.log
        chdir: /opt/app

    - name: Cleanup
      ansible.builtin.file:  # Better: use file module
        path: /tmp/app.tar.gz
        state: absent

Notice: the corrected version also replaces command with more appropriate dedicated modules where possible — another best practice.

Suppressing the Rule

If shorthand is intentional:

- name: Quick command
  ansible.builtin.command: creates=/tmp/flag touch /tmp/flag  # noqa: deprecated-command-syntax

Or in .ansible-lint:

skip_list:
  - deprecated-command-syntax

Conclusion

Ansible Lint rule 302 (deprecated-command-syntax) catches shorthand syntax that mixes module parameters with command strings. The fix is straightforward: move creates, removes, chdir, and other parameters into the args block or use the key-value format. Structured syntax is unambiguous, easier to read, easier to maintain, and works consistently across all Ansible versions. Even better: check if a dedicated module (like get_url, unarchive, or file) can replace the command/shell call entirely.